1 | // Copyright 2020 Yevhenii Reizner |
2 | // |
3 | // Use of this source code is governed by a BSD-style license that can be |
4 | // found in the LICENSE file. |
5 | |
6 | use core::convert::TryFrom; |
7 | |
8 | use crate::{FiniteF32, IntSize, LengthU32, PathBuilder, Point, SaturateRound, Size, Transform}; |
9 | |
10 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
11 | use crate::NoStdFloat; |
12 | |
13 | /// An integer rectangle. |
14 | /// |
15 | /// # Guarantees |
16 | /// |
17 | /// - Width and height are in 1..=i32::MAX range. |
18 | /// - x+width and y+height does not overflow. |
19 | #[allow (missing_docs)] |
20 | #[derive (Copy, Clone, PartialEq, Debug)] |
21 | pub struct IntRect { |
22 | x: i32, |
23 | y: i32, |
24 | width: LengthU32, |
25 | height: LengthU32, |
26 | } |
27 | |
28 | impl IntRect { |
29 | /// Creates a new `IntRect`. |
30 | pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> { |
31 | x.checked_add(i32::try_from(width).ok()?)?; |
32 | y.checked_add(i32::try_from(height).ok()?)?; |
33 | |
34 | Some(IntRect { |
35 | x, |
36 | y, |
37 | width: LengthU32::new(width)?, |
38 | height: LengthU32::new(height)?, |
39 | }) |
40 | } |
41 | |
42 | /// Creates a new `IntRect`. |
43 | pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> { |
44 | let width = u32::try_from(right.checked_sub(left)?).ok()?; |
45 | let height = u32::try_from(bottom.checked_sub(top)?).ok()?; |
46 | IntRect::from_xywh(left, top, width, height) |
47 | } |
48 | |
49 | /// Returns rect's X position. |
50 | pub fn x(&self) -> i32 { |
51 | self.x |
52 | } |
53 | |
54 | /// Returns rect's Y position. |
55 | pub fn y(&self) -> i32 { |
56 | self.y |
57 | } |
58 | |
59 | /// Returns rect's width. |
60 | pub fn width(&self) -> u32 { |
61 | self.width.get() |
62 | } |
63 | |
64 | /// Returns rect's height. |
65 | pub fn height(&self) -> u32 { |
66 | self.height.get() |
67 | } |
68 | |
69 | /// Returns rect's left edge. |
70 | pub fn left(&self) -> i32 { |
71 | self.x |
72 | } |
73 | |
74 | /// Returns rect's top edge. |
75 | pub fn top(&self) -> i32 { |
76 | self.y |
77 | } |
78 | |
79 | /// Returns rect's right edge. |
80 | pub fn right(&self) -> i32 { |
81 | // No overflow is guaranteed by constructors. |
82 | self.x + self.width.get() as i32 |
83 | } |
84 | |
85 | /// Returns rect's bottom edge. |
86 | pub fn bottom(&self) -> i32 { |
87 | // No overflow is guaranteed by constructors. |
88 | self.y + self.height.get() as i32 |
89 | } |
90 | |
91 | /// Returns rect's size. |
92 | pub fn size(&self) -> IntSize { |
93 | IntSize::from_wh_safe(self.width, self.height) |
94 | } |
95 | |
96 | /// Checks that the rect is completely includes `other` Rect. |
97 | pub fn contains(&self, other: &Self) -> bool { |
98 | self.x <= other.x |
99 | && self.y <= other.y |
100 | && self.right() >= other.right() |
101 | && self.bottom() >= other.bottom() |
102 | } |
103 | |
104 | /// Returns an intersection of two rectangles. |
105 | /// |
106 | /// Returns `None` otherwise. |
107 | pub fn intersect(&self, other: &Self) -> Option<Self> { |
108 | let left = self.x.max(other.x); |
109 | let top = self.y.max(other.y); |
110 | |
111 | let right = self.right().min(other.right()); |
112 | let bottom = self.bottom().min(other.bottom()); |
113 | |
114 | let w = u32::try_from(right.checked_sub(left)?).ok()?; |
115 | let h = u32::try_from(bottom.checked_sub(top)?).ok()?; |
116 | |
117 | IntRect::from_xywh(left, top, w, h) |
118 | } |
119 | |
120 | /// Insets the rectangle. |
121 | pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> { |
122 | IntRect::from_ltrb( |
123 | self.left() + dx, |
124 | self.top() + dy, |
125 | self.right() - dx, |
126 | self.bottom() - dy, |
127 | ) |
128 | } |
129 | |
130 | /// Outsets the rectangle. |
131 | pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> { |
132 | IntRect::from_ltrb( |
133 | self.left().saturating_sub(dx), |
134 | self.top().saturating_sub(dy), |
135 | self.right().saturating_add(dx), |
136 | self.bottom().saturating_add(dy), |
137 | ) |
138 | } |
139 | |
140 | /// Translates the rect by the specified offset. |
141 | pub fn translate(&self, tx: i32, ty: i32) -> Option<Self> { |
142 | IntRect::from_xywh(self.x() + tx, self.y() + ty, self.width(), self.height()) |
143 | } |
144 | |
145 | /// Translates the rect to the specified position. |
146 | pub fn translate_to(&self, x: i32, y: i32) -> Option<Self> { |
147 | IntRect::from_xywh(x, y, self.width(), self.height()) |
148 | } |
149 | |
150 | /// Converts into `Rect`. |
151 | pub fn to_rect(&self) -> Rect { |
152 | // Can't fail, because `IntRect` is always valid. |
153 | Rect::from_ltrb( |
154 | self.x as f32, |
155 | self.y as f32, |
156 | self.x as f32 + self.width.get() as f32, |
157 | self.y as f32 + self.height.get() as f32, |
158 | ) |
159 | .unwrap() |
160 | } |
161 | } |
162 | |
163 | #[cfg (test)] |
164 | mod int_rect_tests { |
165 | use super::*; |
166 | |
167 | #[test ] |
168 | fn tests() { |
169 | assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None); |
170 | assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None); |
171 | assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None); |
172 | |
173 | assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, u32::MAX), None); |
174 | assert_eq!(IntRect::from_xywh(0, 0, 1, u32::MAX), None); |
175 | assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, 1), None); |
176 | |
177 | assert_eq!(IntRect::from_xywh(i32::MAX, 0, 1, 1), None); |
178 | assert_eq!(IntRect::from_xywh(0, i32::MAX, 1, 1), None); |
179 | |
180 | { |
181 | // No intersection. |
182 | let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap(); |
183 | let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap(); |
184 | assert_eq!(r1.intersect(&r2), None); |
185 | } |
186 | |
187 | { |
188 | // Second inside the first one. |
189 | let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap(); |
190 | let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap(); |
191 | assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14)); |
192 | } |
193 | |
194 | { |
195 | // Partial overlap. |
196 | let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap(); |
197 | let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap(); |
198 | assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30)); |
199 | } |
200 | } |
201 | } |
202 | |
203 | /// A rectangle defined by left, top, right and bottom edges. |
204 | /// |
205 | /// Can have zero width and/or height. But not a negative one. |
206 | /// |
207 | /// # Guarantees |
208 | /// |
209 | /// - All values are finite. |
210 | /// - Left edge is <= right. |
211 | /// - Top edge is <= bottom. |
212 | /// - Width and height are <= f32::MAX. |
213 | #[allow (missing_docs)] |
214 | #[derive (Copy, Clone, PartialEq)] |
215 | pub struct Rect { |
216 | left: FiniteF32, |
217 | top: FiniteF32, |
218 | right: FiniteF32, |
219 | bottom: FiniteF32, |
220 | } |
221 | |
222 | impl core::fmt::Debug for Rect { |
223 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
224 | f&mut DebugStruct<'_, '_>.debug_struct("Rect" ) |
225 | .field("left" , &self.left.get()) |
226 | .field("top" , &self.top.get()) |
227 | .field("right" , &self.right.get()) |
228 | .field(name:"bottom" , &self.bottom.get()) |
229 | .finish() |
230 | } |
231 | } |
232 | |
233 | impl Rect { |
234 | /// Creates new `Rect`. |
235 | pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> { |
236 | let left = FiniteF32::new(left)?; |
237 | let top = FiniteF32::new(top)?; |
238 | let right = FiniteF32::new(right)?; |
239 | let bottom = FiniteF32::new(bottom)?; |
240 | |
241 | if left.get() <= right.get() && top.get() <= bottom.get() { |
242 | // Width and height must not overflow. |
243 | checked_f32_sub(right.get(), left.get())?; |
244 | checked_f32_sub(bottom.get(), top.get())?; |
245 | |
246 | Some(Rect { |
247 | left, |
248 | top, |
249 | right, |
250 | bottom, |
251 | }) |
252 | } else { |
253 | None |
254 | } |
255 | } |
256 | |
257 | /// Creates new `Rect`. |
258 | pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> { |
259 | Rect::from_ltrb(x, y, w + x, h + y) |
260 | } |
261 | |
262 | /// Returns the left edge. |
263 | pub fn left(&self) -> f32 { |
264 | self.left.get() |
265 | } |
266 | |
267 | /// Returns the top edge. |
268 | pub fn top(&self) -> f32 { |
269 | self.top.get() |
270 | } |
271 | |
272 | /// Returns the right edge. |
273 | pub fn right(&self) -> f32 { |
274 | self.right.get() |
275 | } |
276 | |
277 | /// Returns the bottom edge. |
278 | pub fn bottom(&self) -> f32 { |
279 | self.bottom.get() |
280 | } |
281 | |
282 | /// Returns rect's X position. |
283 | pub fn x(&self) -> f32 { |
284 | self.left.get() |
285 | } |
286 | |
287 | /// Returns rect's Y position. |
288 | pub fn y(&self) -> f32 { |
289 | self.top.get() |
290 | } |
291 | |
292 | /// Returns rect's width. |
293 | #[inline ] |
294 | pub fn width(&self) -> f32 { |
295 | self.right.get() - self.left.get() |
296 | } |
297 | |
298 | /// Returns rect's height. |
299 | #[inline ] |
300 | pub fn height(&self) -> f32 { |
301 | self.bottom.get() - self.top.get() |
302 | } |
303 | |
304 | /// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion. |
305 | /// |
306 | /// Width and height are guarantee to be >= 1. |
307 | pub fn round(&self) -> Option<IntRect> { |
308 | IntRect::from_xywh( |
309 | i32::saturate_round(self.x()), |
310 | i32::saturate_round(self.y()), |
311 | core::cmp::max(1, i32::saturate_round(self.width()) as u32), |
312 | core::cmp::max(1, i32::saturate_round(self.height()) as u32), |
313 | ) |
314 | } |
315 | |
316 | /// Converts into an `IntRect` rounding outwards. |
317 | /// |
318 | /// Width and height are guarantee to be >= 1. |
319 | pub fn round_out(&self) -> Option<IntRect> { |
320 | IntRect::from_xywh( |
321 | i32::saturate_floor(self.x()), |
322 | i32::saturate_floor(self.y()), |
323 | core::cmp::max(1, i32::saturate_ceil(self.width()) as u32), |
324 | core::cmp::max(1, i32::saturate_ceil(self.height()) as u32), |
325 | ) |
326 | } |
327 | |
328 | /// Returns an intersection of two rectangles. |
329 | /// |
330 | /// Returns `None` otherwise. |
331 | pub fn intersect(&self, other: &Self) -> Option<Self> { |
332 | let left = self.x().max(other.x()); |
333 | let top = self.y().max(other.y()); |
334 | |
335 | let right = self.right().min(other.right()); |
336 | let bottom = self.bottom().min(other.bottom()); |
337 | |
338 | Rect::from_ltrb(left, top, right, bottom) |
339 | } |
340 | |
341 | /// Creates a Rect from Point array. |
342 | /// |
343 | /// Returns None if count is zero or if Point array contains an infinity or NaN. |
344 | pub fn from_points(points: &[Point]) -> Option<Self> { |
345 | use crate::f32x4_t::f32x4; |
346 | |
347 | if points.is_empty() { |
348 | return None; |
349 | } |
350 | |
351 | let mut offset = 0; |
352 | let mut min; |
353 | let mut max; |
354 | if points.len() & 1 != 0 { |
355 | let pt = points[0]; |
356 | min = f32x4([pt.x, pt.y, pt.x, pt.y]); |
357 | max = min; |
358 | offset += 1; |
359 | } else { |
360 | let pt0 = points[0]; |
361 | let pt1 = points[1]; |
362 | min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]); |
363 | max = min; |
364 | offset += 2; |
365 | } |
366 | |
367 | let mut accum = f32x4::default(); |
368 | while offset != points.len() { |
369 | let pt0 = points[offset + 0]; |
370 | let pt1 = points[offset + 1]; |
371 | let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]); |
372 | |
373 | accum *= xy; |
374 | min = min.min(xy); |
375 | max = max.max(xy); |
376 | offset += 2; |
377 | } |
378 | |
379 | let all_finite = accum * f32x4::default() == f32x4::default(); |
380 | let min: [f32; 4] = min.0; |
381 | let max: [f32; 4] = max.0; |
382 | if all_finite { |
383 | Rect::from_ltrb( |
384 | min[0].min(min[2]), |
385 | min[1].min(min[3]), |
386 | max[0].max(max[2]), |
387 | max[1].max(max[3]), |
388 | ) |
389 | } else { |
390 | None |
391 | } |
392 | } |
393 | |
394 | /// Insets the rectangle by the specified offset. |
395 | pub fn inset(&self, dx: f32, dy: f32) -> Option<Self> { |
396 | Rect::from_ltrb( |
397 | self.left() + dx, |
398 | self.top() + dy, |
399 | self.right() - dx, |
400 | self.bottom() - dy, |
401 | ) |
402 | } |
403 | |
404 | /// Outsets the rectangle by the specified offset. |
405 | pub fn outset(&self, dx: f32, dy: f32) -> Option<Self> { |
406 | self.inset(-dx, -dy) |
407 | } |
408 | |
409 | /// Transforms the rect using the provided `Transform`. |
410 | /// |
411 | /// This method is expensive. |
412 | pub fn transform(&self, ts: Transform) -> Option<Self> { |
413 | if !ts.is_identity() { |
414 | // TODO: remove allocation |
415 | let mut path = PathBuilder::from_rect(*self); |
416 | path = path.transform(ts)?; |
417 | Some(path.bounds()) |
418 | } else { |
419 | Some(*self) |
420 | } |
421 | } |
422 | |
423 | /// Applies a bounding box transform. |
424 | pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self { |
425 | let x = self.x() * bbox.width() + bbox.x(); |
426 | let y = self.y() * bbox.height() + bbox.y(); |
427 | let w = self.width() * bbox.width(); |
428 | let h = self.height() * bbox.height(); |
429 | Self::from_xywh(x, y, w, h).unwrap() |
430 | } |
431 | |
432 | /// Converts into [`NonZeroRect`]. |
433 | pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> { |
434 | NonZeroRect::from_xywh(self.x(), self.y(), self.width(), self.height()) |
435 | } |
436 | } |
437 | |
438 | fn checked_f32_sub(a: f32, b: f32) -> Option<f32> { |
439 | debug_assert!(a.is_finite()); |
440 | debug_assert!(b.is_finite()); |
441 | |
442 | let n: f64 = a as f64 - b as f64; |
443 | // Not sure if this is perfectly correct. |
444 | if n > f32::MIN as f64 && n < f32::MAX as f64 { |
445 | Some(n as f32) |
446 | } else { |
447 | None |
448 | } |
449 | } |
450 | |
451 | #[cfg (test)] |
452 | mod rect_tests { |
453 | use super::*; |
454 | |
455 | #[test ] |
456 | fn tests() { |
457 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None); |
458 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None); |
459 | assert_eq!(Rect::from_ltrb(f32::NAN, 10.0, 10.0, 10.0), None); |
460 | assert_eq!(Rect::from_ltrb(10.0, f32::NAN, 10.0, 10.0), None); |
461 | assert_eq!(Rect::from_ltrb(10.0, 10.0, f32::NAN, 10.0), None); |
462 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::NAN), None); |
463 | assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::INFINITY), None); |
464 | |
465 | let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap(); |
466 | assert_eq!(rect.left(), 10.0); |
467 | assert_eq!(rect.top(), 20.0); |
468 | assert_eq!(rect.right(), 30.0); |
469 | assert_eq!(rect.bottom(), 40.0); |
470 | assert_eq!(rect.width(), 20.0); |
471 | assert_eq!(rect.height(), 20.0); |
472 | |
473 | let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap(); |
474 | assert_eq!(rect.width(), 20.0); |
475 | assert_eq!(rect.height(), 20.0); |
476 | } |
477 | |
478 | #[test ] |
479 | fn round_overflow() { |
480 | // minimum value that cause overflow |
481 | // because i32::MAX has no exact conversion to f32 |
482 | let x = 128.0; |
483 | // maximum width |
484 | let width = i32::MAX as f32; |
485 | |
486 | let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap(); |
487 | assert_eq!(rect.round(), None); |
488 | assert_eq!(rect.round_out(), None); |
489 | } |
490 | } |
491 | |
492 | /// A rectangle defined by left, top, right and bottom edges. |
493 | /// |
494 | /// Similar to [`Rect`], but width and height guarantee to be non-zero and positive. |
495 | /// |
496 | /// # Guarantees |
497 | /// |
498 | /// - All values are finite. |
499 | /// - Left edge is < right. |
500 | /// - Top edge is < bottom. |
501 | /// - Width and height are <= f32::MAX. |
502 | /// - Width and height are > 0.0 |
503 | #[allow (missing_docs)] |
504 | #[derive (Copy, Clone, PartialEq)] |
505 | pub struct NonZeroRect { |
506 | left: FiniteF32, |
507 | top: FiniteF32, |
508 | right: FiniteF32, |
509 | bottom: FiniteF32, |
510 | } |
511 | |
512 | impl core::fmt::Debug for NonZeroRect { |
513 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
514 | f&mut DebugStruct<'_, '_>.debug_struct("NonZeroRect" ) |
515 | .field("left" , &self.left.get()) |
516 | .field("top" , &self.top.get()) |
517 | .field("right" , &self.right.get()) |
518 | .field(name:"bottom" , &self.bottom.get()) |
519 | .finish() |
520 | } |
521 | } |
522 | |
523 | impl NonZeroRect { |
524 | /// Creates new `NonZeroRect`. |
525 | pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> { |
526 | let left = FiniteF32::new(left)?; |
527 | let top = FiniteF32::new(top)?; |
528 | let right = FiniteF32::new(right)?; |
529 | let bottom = FiniteF32::new(bottom)?; |
530 | |
531 | if left.get() < right.get() && top.get() < bottom.get() { |
532 | // Width and height must not overflow. |
533 | checked_f32_sub(right.get(), left.get())?; |
534 | checked_f32_sub(bottom.get(), top.get())?; |
535 | |
536 | Some(Self { |
537 | left, |
538 | top, |
539 | right, |
540 | bottom, |
541 | }) |
542 | } else { |
543 | None |
544 | } |
545 | } |
546 | |
547 | /// Creates new `NonZeroRect`. |
548 | pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> { |
549 | Self::from_ltrb(x, y, w + x, h + y) |
550 | } |
551 | |
552 | /// Returns the left edge. |
553 | pub fn left(&self) -> f32 { |
554 | self.left.get() |
555 | } |
556 | |
557 | /// Returns the top edge. |
558 | pub fn top(&self) -> f32 { |
559 | self.top.get() |
560 | } |
561 | |
562 | /// Returns the right edge. |
563 | pub fn right(&self) -> f32 { |
564 | self.right.get() |
565 | } |
566 | |
567 | /// Returns the bottom edge. |
568 | pub fn bottom(&self) -> f32 { |
569 | self.bottom.get() |
570 | } |
571 | |
572 | /// Returns rect's X position. |
573 | pub fn x(&self) -> f32 { |
574 | self.left.get() |
575 | } |
576 | |
577 | /// Returns rect's Y position. |
578 | pub fn y(&self) -> f32 { |
579 | self.top.get() |
580 | } |
581 | |
582 | /// Returns rect's width. |
583 | pub fn width(&self) -> f32 { |
584 | self.right.get() - self.left.get() |
585 | } |
586 | |
587 | /// Returns rect's height. |
588 | pub fn height(&self) -> f32 { |
589 | self.bottom.get() - self.top.get() |
590 | } |
591 | |
592 | /// Returns rect's size. |
593 | pub fn size(&self) -> Size { |
594 | Size::from_wh(self.width(), self.height()).unwrap() |
595 | } |
596 | |
597 | /// Translates the rect to the specified position. |
598 | pub fn translate_to(&self, x: f32, y: f32) -> Option<Self> { |
599 | Self::from_xywh(x, y, self.width(), self.height()) |
600 | } |
601 | |
602 | /// Transforms the rect using the provided `Transform`. |
603 | /// |
604 | /// This method is expensive. |
605 | pub fn transform(&self, ts: Transform) -> Option<Self> { |
606 | if !ts.is_identity() { |
607 | // TODO: remove allocation |
608 | let mut path = PathBuilder::from_rect(self.to_rect()); |
609 | path = path.transform(ts)?; |
610 | path.bounds().to_non_zero_rect() |
611 | } else { |
612 | Some(*self) |
613 | } |
614 | } |
615 | |
616 | /// Applies a bounding box transform. |
617 | pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self { |
618 | let x = self.x() * bbox.width() + bbox.x(); |
619 | let y = self.y() * bbox.height() + bbox.y(); |
620 | let w = self.width() * bbox.width(); |
621 | let h = self.height() * bbox.height(); |
622 | Self::from_xywh(x, y, w, h).unwrap() |
623 | } |
624 | |
625 | /// Converts into [`Rect`]. |
626 | pub fn to_rect(&self) -> Rect { |
627 | Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap() |
628 | } |
629 | |
630 | /// Converts into [`IntRect`]. |
631 | pub fn to_int_rect(&self) -> IntRect { |
632 | IntRect::from_xywh( |
633 | self.x().floor() as i32, |
634 | self.y().floor() as i32, |
635 | core::cmp::max(1, self.width().ceil() as u32), |
636 | core::cmp::max(1, self.height().ceil() as u32), |
637 | ) |
638 | .unwrap() |
639 | } |
640 | } |
641 | |