1use crate::error::{OxiGdalError, Result};
4use core::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct RasterWindow {
10 pub col_off: u32,
12 pub row_off: u32,
14 pub width: u32,
16 pub height: u32,
18}
19
20impl RasterWindow {
21 pub fn new(col_off: u32, row_off: u32, width: u32, height: u32) -> Result<Self> {
23 if width == 0 {
24 return Err(OxiGdalError::invalid_parameter(
25 "width",
26 "window width must be greater than 0",
27 ));
28 }
29 if height == 0 {
30 return Err(OxiGdalError::invalid_parameter(
31 "height",
32 "window height must be greater than 0",
33 ));
34 }
35 Ok(Self {
36 col_off,
37 row_off,
38 width,
39 height,
40 })
41 }
42
43 pub fn full(raster_width: u32, raster_height: u32) -> Result<Self> {
45 Self::new(0, 0, raster_width, raster_height)
46 }
47
48 pub fn fits_within(&self, raster_width: u32, raster_height: u32) -> bool {
50 let col_end = u64::from(self.col_off) + u64::from(self.width);
51 let row_end = u64::from(self.row_off) + u64::from(self.height);
52 col_end <= u64::from(raster_width) && row_end <= u64::from(raster_height)
53 }
54
55 pub fn validate_bounds(&self, raster_width: u32, raster_height: u32) -> Result<()> {
58 if !self.fits_within(raster_width, raster_height) {
59 return Err(OxiGdalError::OutOfBounds {
60 message: format!(
61 "Window (col_off={}, row_off={}, width={}, height={}) exceeds raster bounds ({}x{})",
62 self.col_off,
63 self.row_off,
64 self.width,
65 self.height,
66 raster_width,
67 raster_height
68 ),
69 });
70 }
71 Ok(())
72 }
73
74 pub fn pixel_count(&self) -> u64 {
76 u64::from(self.width) * u64::from(self.height)
77 }
78
79 pub fn intersection(&self, other: &RasterWindow) -> Option<RasterWindow> {
82 let left = self.col_off.max(other.col_off);
83 let top = self.row_off.max(other.row_off);
84
85 let self_right = self.col_off.checked_add(self.width)?;
86 let other_right = other.col_off.checked_add(other.width)?;
87 let right = self_right.min(other_right);
88
89 let self_bottom = self.row_off.checked_add(self.height)?;
90 let other_bottom = other.row_off.checked_add(other.height)?;
91 let bottom = self_bottom.min(other_bottom);
92
93 if left >= right || top >= bottom {
94 return None;
95 }
96
97 Some(RasterWindow {
99 col_off: left,
100 row_off: top,
101 width: right - left,
102 height: bottom - top,
103 })
104 }
105
106 pub fn union_bounds(&self, other: &RasterWindow) -> RasterWindow {
108 let left = self.col_off.min(other.col_off);
109 let top = self.row_off.min(other.row_off);
110
111 let self_right = u64::from(self.col_off) + u64::from(self.width);
112 let other_right = u64::from(other.col_off) + u64::from(other.width);
113 let right = self_right.max(other_right);
114
115 let self_bottom = u64::from(self.row_off) + u64::from(self.height);
116 let other_bottom = u64::from(other.row_off) + u64::from(other.height);
117 let bottom = self_bottom.max(other_bottom);
118
119 let width = u32::try_from(right - u64::from(left)).unwrap_or(u32::MAX);
121 let height = u32::try_from(bottom - u64::from(top)).unwrap_or(u32::MAX);
122
123 RasterWindow {
124 col_off: left,
125 row_off: top,
126 width,
127 height,
128 }
129 }
130
131 pub fn contains_pixel(&self, col: u32, row: u32) -> bool {
133 col >= self.col_off
134 && row >= self.row_off
135 && u64::from(col) < u64::from(self.col_off) + u64::from(self.width)
136 && u64::from(row) < u64::from(self.row_off) + u64::from(self.height)
137 }
138
139 pub fn subdivide(&self, tile_width: u32, tile_height: u32) -> Result<Vec<RasterWindow>> {
142 if tile_width == 0 {
143 return Err(OxiGdalError::invalid_parameter(
144 "tile_width",
145 "tile width must be greater than 0",
146 ));
147 }
148 if tile_height == 0 {
149 return Err(OxiGdalError::invalid_parameter(
150 "tile_height",
151 "tile height must be greater than 0",
152 ));
153 }
154
155 let cols = self.width.div_ceil(tile_width);
156 let rows = self.height.div_ceil(tile_height);
157 let capacity = u64::from(cols) * u64::from(rows);
158
159 let mut tiles = Vec::with_capacity(usize::try_from(capacity).map_err(|_| {
160 OxiGdalError::invalid_parameter("tile_size", "too many tiles would be generated")
161 })?);
162
163 for row_idx in 0..rows {
164 for col_idx in 0..cols {
165 let c = self.col_off + col_idx * tile_width;
166 let r = self.row_off + row_idx * tile_height;
167 let w = tile_width.min(self.col_off + self.width - c);
168 let h = tile_height.min(self.row_off + self.height - r);
169 tiles.push(RasterWindow {
170 col_off: c,
171 row_off: r,
172 width: w,
173 height: h,
174 });
175 }
176 }
177
178 Ok(tiles)
179 }
180
181 pub fn to_global(&self, local_col: u32, local_row: u32) -> Result<(u32, u32)> {
183 if local_col >= self.width || local_row >= self.height {
184 return Err(OxiGdalError::OutOfBounds {
185 message: format!(
186 "Local coordinate ({}, {}) is outside window dimensions ({}x{})",
187 local_col, local_row, self.width, self.height
188 ),
189 });
190 }
191 let global_col =
192 self.col_off
193 .checked_add(local_col)
194 .ok_or_else(|| OxiGdalError::OutOfBounds {
195 message: "Global column coordinate overflow".to_string(),
196 })?;
197 let global_row =
198 self.row_off
199 .checked_add(local_row)
200 .ok_or_else(|| OxiGdalError::OutOfBounds {
201 message: "Global row coordinate overflow".to_string(),
202 })?;
203 Ok((global_col, global_row))
204 }
205
206 pub fn to_local(&self, global_col: u32, global_row: u32) -> Option<(u32, u32)> {
209 if !self.contains_pixel(global_col, global_row) {
210 return None;
211 }
212 Some((global_col - self.col_off, global_row - self.row_off))
213 }
214
215 pub fn extract_from_buffer(
219 &self,
220 source: &[u8],
221 raster_width: u32,
222 bytes_per_pixel: u32,
223 ) -> Result<Vec<u8>> {
224 if bytes_per_pixel == 0 {
225 return Err(OxiGdalError::invalid_parameter(
226 "bytes_per_pixel",
227 "must be greater than 0",
228 ));
229 }
230
231 let rw = u64::from(raster_width);
232 let bpp = u64::from(bytes_per_pixel);
233 let row_stride = rw.checked_mul(bpp).ok_or_else(|| {
234 OxiGdalError::invalid_parameter("raster_width", "row stride overflow")
235 })?;
236
237 let last_row = u64::from(self.row_off) + u64::from(self.height);
239 let required_len = last_row.checked_mul(row_stride).ok_or_else(|| {
240 OxiGdalError::invalid_parameter("raster_width", "buffer size calculation overflow")
241 })?;
242 if u64::try_from(source.len()).unwrap_or(0) < required_len {
243 return Err(OxiGdalError::OutOfBounds {
244 message: format!(
245 "Source buffer too small: need {} bytes, got {}",
246 required_len,
247 source.len()
248 ),
249 });
250 }
251
252 let col_end = u64::from(self.col_off) + u64::from(self.width);
254 if col_end > rw {
255 return Err(OxiGdalError::OutOfBounds {
256 message: format!(
257 "Window column extent {} exceeds raster width {}",
258 col_end, raster_width
259 ),
260 });
261 }
262
263 let win_row_bytes = u64::from(self.width) * bpp;
264 let total_bytes = win_row_bytes * u64::from(self.height);
265 let total_usize = usize::try_from(total_bytes).map_err(|_| {
266 OxiGdalError::invalid_parameter("window", "window data size exceeds addressable memory")
267 })?;
268
269 let mut result = Vec::with_capacity(total_usize);
270
271 for row in 0..self.height {
272 let src_row = u64::from(self.row_off + row);
273 let src_offset = src_row * row_stride + u64::from(self.col_off) * bpp;
274 let start = src_offset as usize;
275 let end = start + win_row_bytes as usize;
276 result.extend_from_slice(&source[start..end]);
277 }
278
279 Ok(result)
280 }
281
282 pub fn write_to_buffer(
284 &self,
285 window_data: &[u8],
286 dest: &mut [u8],
287 raster_width: u32,
288 bytes_per_pixel: u32,
289 ) -> Result<()> {
290 if bytes_per_pixel == 0 {
291 return Err(OxiGdalError::invalid_parameter(
292 "bytes_per_pixel",
293 "must be greater than 0",
294 ));
295 }
296
297 let rw = u64::from(raster_width);
298 let bpp = u64::from(bytes_per_pixel);
299 let row_stride = rw.checked_mul(bpp).ok_or_else(|| {
300 OxiGdalError::invalid_parameter("raster_width", "row stride overflow")
301 })?;
302
303 let win_row_bytes = u64::from(self.width) * bpp;
304 let expected_data_len = win_row_bytes * u64::from(self.height);
305 if u64::try_from(window_data.len()).unwrap_or(0) != expected_data_len {
306 return Err(OxiGdalError::invalid_parameter(
307 "window_data",
308 format!(
309 "expected {} bytes, got {}",
310 expected_data_len,
311 window_data.len()
312 ),
313 ));
314 }
315
316 let last_row = u64::from(self.row_off) + u64::from(self.height);
318 let required_len = last_row.checked_mul(row_stride).ok_or_else(|| {
319 OxiGdalError::invalid_parameter("raster_width", "buffer size calculation overflow")
320 })?;
321 if u64::try_from(dest.len()).unwrap_or(0) < required_len {
322 return Err(OxiGdalError::OutOfBounds {
323 message: format!(
324 "Destination buffer too small: need {} bytes, got {}",
325 required_len,
326 dest.len()
327 ),
328 });
329 }
330
331 let col_end = u64::from(self.col_off) + u64::from(self.width);
333 if col_end > rw {
334 return Err(OxiGdalError::OutOfBounds {
335 message: format!(
336 "Window column extent {} exceeds raster width {}",
337 col_end, raster_width
338 ),
339 });
340 }
341
342 for row in 0..self.height {
343 let dst_row = u64::from(self.row_off + row);
344 let dst_offset = dst_row * row_stride + u64::from(self.col_off) * bpp;
345 let dst_start = dst_offset as usize;
346 let dst_end = dst_start + win_row_bytes as usize;
347
348 let src_offset = u64::from(row) * win_row_bytes;
349 let src_start = src_offset as usize;
350 let src_end = src_start + win_row_bytes as usize;
351
352 dest[dst_start..dst_end].copy_from_slice(&window_data[src_start..src_end]);
353 }
354
355 Ok(())
356 }
357}
358
359impl fmt::Display for RasterWindow {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 write!(
362 f,
363 "Window(col_off={}, row_off={}, width={}, height={})",
364 self.col_off, self.row_off, self.width, self.height
365 )
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_new_valid() {
375 let w = RasterWindow::new(0, 0, 100, 100).expect("should create valid window");
376 assert_eq!(w.col_off, 0);
377 assert_eq!(w.row_off, 0);
378 assert_eq!(w.width, 100);
379 assert_eq!(w.height, 100);
380 }
381
382 #[test]
383 fn test_new_zero_width_error() {
384 let result = RasterWindow::new(0, 0, 0, 100);
385 assert!(result.is_err());
386 }
387
388 #[test]
389 fn test_new_zero_height_error() {
390 let result = RasterWindow::new(0, 0, 100, 0);
391 assert!(result.is_err());
392 }
393
394 #[test]
395 fn test_full() {
396 let w = RasterWindow::full(256, 256).expect("should create full window");
397 assert_eq!(w.col_off, 0);
398 assert_eq!(w.row_off, 0);
399 assert_eq!(w.width, 256);
400 assert_eq!(w.height, 256);
401 }
402
403 #[test]
404 fn test_fits_within() {
405 let w = RasterWindow::new(10, 10, 50, 50).expect("valid window");
406 assert!(w.fits_within(100, 100));
407 assert!(!w.fits_within(30, 30));
408 }
409
410 #[test]
411 fn test_validate_bounds_ok() {
412 let w = RasterWindow::new(0, 0, 100, 100).expect("valid window");
413 assert!(w.validate_bounds(100, 100).is_ok());
414 }
415
416 #[test]
417 fn test_validate_bounds_overflow() {
418 let w = RasterWindow::new(50, 50, 100, 100).expect("valid window");
419 assert!(w.validate_bounds(100, 100).is_err());
420 }
421
422 #[test]
423 fn test_pixel_count() {
424 let w = RasterWindow::new(0, 0, 50, 50).expect("valid window");
425 assert_eq!(w.pixel_count(), 2500);
426 }
427
428 #[test]
429 fn test_intersection_overlap() {
430 let a = RasterWindow::new(0, 0, 100, 100).expect("valid window");
431 let b = RasterWindow::new(50, 50, 100, 100).expect("valid window");
432 let isect = a.intersection(&b).expect("should overlap");
433 assert_eq!(isect.col_off, 50);
434 assert_eq!(isect.row_off, 50);
435 assert_eq!(isect.width, 50);
436 assert_eq!(isect.height, 50);
437 }
438
439 #[test]
440 fn test_intersection_no_overlap() {
441 let a = RasterWindow::new(0, 0, 50, 50).expect("valid window");
442 let b = RasterWindow::new(100, 100, 50, 50).expect("valid window");
443 assert!(a.intersection(&b).is_none());
444 }
445
446 #[test]
447 fn test_union_bounds() {
448 let a = RasterWindow::new(10, 10, 40, 40).expect("valid window");
449 let b = RasterWindow::new(30, 30, 60, 60).expect("valid window");
450 let u = a.union_bounds(&b);
451 assert_eq!(u.col_off, 10);
452 assert_eq!(u.row_off, 10);
453 assert_eq!(u.width, 80);
454 assert_eq!(u.height, 80);
455 }
456
457 #[test]
458 fn test_contains_pixel() {
459 let w = RasterWindow::new(10, 10, 50, 50).expect("valid window");
460 assert!(w.contains_pixel(10, 10));
461 assert!(w.contains_pixel(59, 59));
462 assert!(!w.contains_pixel(60, 60));
463 assert!(!w.contains_pixel(9, 9));
464 }
465
466 #[test]
467 fn test_subdivide() {
468 let w = RasterWindow::new(0, 0, 100, 100).expect("valid window");
469 let tiles = w.subdivide(30, 30).expect("should subdivide");
470 assert_eq!(tiles.len(), 16);
472
473 assert_eq!(tiles[0].col_off, 0);
475 assert_eq!(tiles[0].row_off, 0);
476 assert_eq!(tiles[0].width, 30);
477 assert_eq!(tiles[0].height, 30);
478
479 assert_eq!(tiles[3].col_off, 90);
481 assert_eq!(tiles[3].row_off, 0);
482 assert_eq!(tiles[3].width, 10);
483 assert_eq!(tiles[3].height, 30);
484
485 assert_eq!(tiles[15].col_off, 90);
487 assert_eq!(tiles[15].row_off, 90);
488 assert_eq!(tiles[15].width, 10);
489 assert_eq!(tiles[15].height, 10);
490 }
491
492 #[test]
493 fn test_to_global_and_local() {
494 let w = RasterWindow::new(10, 20, 50, 50).expect("valid window");
495
496 let (gc, gr) = w.to_global(5, 3).expect("should convert");
498 assert_eq!((gc, gr), (15, 23));
499
500 let local = w.to_local(15, 23).expect("should be inside");
502 assert_eq!(local, (5, 3));
503
504 assert!(w.to_local(0, 0).is_none());
506
507 assert!(w.to_global(50, 50).is_err());
509 }
510
511 #[test]
512 fn test_extract_from_buffer() {
513 let source: Vec<u8> = (0u8..16).collect();
515 let w = RasterWindow::new(1, 1, 2, 2).expect("valid window");
516 let extracted = w
517 .extract_from_buffer(&source, 4, 1)
518 .expect("should extract");
519
520 assert_eq!(extracted, vec![5, 6, 9, 10]);
523 }
524
525 #[test]
526 fn test_write_to_buffer() {
527 let mut dest = vec![0u8; 16];
529 let w = RasterWindow::new(1, 1, 2, 2).expect("valid window");
530 let window_data = vec![0xAA, 0xBB, 0xCC, 0xDD];
531 w.write_to_buffer(&window_data, &mut dest, 4, 1)
532 .expect("should write");
533
534 #[rustfmt::skip]
536 let expected: Vec<u8> = vec![
537 0x00, 0x00, 0x00, 0x00,
538 0x00, 0xAA, 0xBB, 0x00,
539 0x00, 0xCC, 0xDD, 0x00,
540 0x00, 0x00, 0x00, 0x00,
541 ];
542 assert_eq!(dest, expected);
543 }
544
545 #[test]
546 fn test_display() {
547 let w = RasterWindow::new(5, 10, 100, 200).expect("valid window");
548 assert_eq!(
549 w.to_string(),
550 "Window(col_off=5, row_off=10, width=100, height=200)"
551 );
552 }
553}