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