use crate::api::TileSize;
use crate::api::units::*;
use crate::segment::EdgeAaSegmentMask;
use euclid::{point2, size2};
use std::i32;
use std::ops::Range;
pub fn simplify_repeated_primitive(
stretch_size: &LayoutSize,
tile_spacing: &mut LayoutSize,
prim_rect: &mut LayoutRect,
) {
let stride = *stretch_size + *tile_spacing;
if stride.width >= prim_rect.width() {
tile_spacing.width = 0.0;
prim_rect.max.x = f32::min(prim_rect.min.x + stretch_size.width, prim_rect.max.x);
}
if stride.height >= prim_rect.height() {
tile_spacing.height = 0.0;
prim_rect.max.y = f32::min(prim_rect.min.y + stretch_size.height, prim_rect.max.y);
}
}
pub struct Repetition {
pub origin: LayoutPoint,
pub edge_flags: EdgeAaSegmentMask,
}
pub struct RepetitionIterator {
current_x: i32,
x_count: i32,
current_y: i32,
y_count: i32,
row_flags: EdgeAaSegmentMask,
current_origin: LayoutPoint,
initial_origin: LayoutPoint,
stride: LayoutSize,
}
impl RepetitionIterator {
pub fn num_repetitions(&self) -> usize {
(self.y_count * self.x_count) as usize
}
}
impl Iterator for RepetitionIterator {
type Item = Repetition;
fn next(&mut self) -> Option<Self::Item> {
if self.current_x == self.x_count {
self.current_y += 1;
if self.current_y >= self.y_count {
return None;
}
self.current_x = 0;
self.row_flags = EdgeAaSegmentMask::empty();
if self.current_y == self.y_count - 1 {
self.row_flags |= EdgeAaSegmentMask::BOTTOM;
}
self.current_origin.x = self.initial_origin.x;
self.current_origin.y += self.stride.height;
}
let mut edge_flags = self.row_flags;
if self.current_x == 0 {
edge_flags |= EdgeAaSegmentMask::LEFT;
}
if self.current_x == self.x_count - 1 {
edge_flags |= EdgeAaSegmentMask::RIGHT;
}
let repetition = Repetition {
origin: self.current_origin,
edge_flags,
};
self.current_origin.x += self.stride.width;
self.current_x += 1;
Some(repetition)
}
}
pub fn repetitions(
prim_rect: &LayoutRect,
visible_rect: &LayoutRect,
stride: LayoutSize,
) -> RepetitionIterator {
let visible_rect = match prim_rect.intersection(&visible_rect) {
Some(rect) => rect,
None => {
return RepetitionIterator {
current_origin: LayoutPoint::zero(),
initial_origin: LayoutPoint::zero(),
current_x: 0,
current_y: 0,
x_count: 0,
y_count: 0,
stride,
row_flags: EdgeAaSegmentMask::empty(),
}
}
};
assert!(stride.width > 0.0);
assert!(stride.height > 0.0);
let nx = if visible_rect.min.x > prim_rect.min.x {
f32::floor((visible_rect.min.x - prim_rect.min.x) / stride.width)
} else {
0.0
};
let ny = if visible_rect.min.y > prim_rect.min.y {
f32::floor((visible_rect.min.y - prim_rect.min.y) / stride.height)
} else {
0.0
};
let x0 = prim_rect.min.x + nx * stride.width;
let y0 = prim_rect.min.y + ny * stride.height;
let x_most = visible_rect.max.x;
let y_most = visible_rect.max.y;
let x_count = f32::ceil((x_most - x0) / stride.width) as i32;
let y_count = f32::ceil((y_most - y0) / stride.height) as i32;
let mut row_flags = EdgeAaSegmentMask::TOP;
if y_count == 1 {
row_flags |= EdgeAaSegmentMask::BOTTOM;
}
RepetitionIterator {
current_origin: LayoutPoint::new(x0, y0),
initial_origin: LayoutPoint::new(x0, y0),
current_x: 0,
current_y: 0,
x_count,
y_count,
row_flags,
stride,
}
}
#[derive(Debug)]
pub struct Tile {
pub rect: LayoutRect,
pub offset: TileOffset,
pub edge_flags: EdgeAaSegmentMask,
}
#[derive(Debug)]
pub struct TileIteratorExtent {
tile_range: Range<i32>,
image_tiles: Range<i32>,
first_tile_layout_size: f32,
last_tile_layout_size: f32,
layout_tiling_origin: f32,
layout_prim_start: f32,
}
#[derive(Debug)]
pub struct TileIterator {
current_tile: TileOffset,
x: TileIteratorExtent,
y: TileIteratorExtent,
regular_tile_size: LayoutSize,
}
impl Iterator for TileIterator {
type Item = Tile;
fn next(&mut self) -> Option<Self::Item> {
if self.current_tile.x >= self.x.tile_range.end {
self.current_tile.y += 1;
self.current_tile.x = self.x.tile_range.start;
}
if self.current_tile.x >= self.x.tile_range.end || self.current_tile.y >= self.y.tile_range.end {
return None;
}
let tile_offset = self.current_tile;
let mut segment_rect = LayoutRect::from_origin_and_size(
LayoutPoint::new(
self.x.layout_tiling_origin + tile_offset.x as f32 * self.regular_tile_size.width,
self.y.layout_tiling_origin + tile_offset.y as f32 * self.regular_tile_size.height,
),
self.regular_tile_size,
);
let mut edge_flags = EdgeAaSegmentMask::empty();
if tile_offset.x == self.x.image_tiles.start {
edge_flags |= EdgeAaSegmentMask::LEFT;
segment_rect.min.x = self.x.layout_prim_start;
segment_rect.max.x = segment_rect.min.x + self.x.first_tile_layout_size;
}
if tile_offset.x == self.x.image_tiles.end - 1 {
edge_flags |= EdgeAaSegmentMask::RIGHT;
segment_rect.max.x = segment_rect.min.x + self.x.last_tile_layout_size;
}
if tile_offset.y == self.y.image_tiles.start {
segment_rect.min.y = self.y.layout_prim_start;
segment_rect.max.y = segment_rect.min.y + self.y.first_tile_layout_size;
edge_flags |= EdgeAaSegmentMask::TOP;
}
if tile_offset.y == self.y.image_tiles.end - 1 {
segment_rect.max.y = segment_rect.min.y + self.y.last_tile_layout_size;
edge_flags |= EdgeAaSegmentMask::BOTTOM;
}
assert!(tile_offset.y < self.y.tile_range.end);
let tile = Tile {
rect: segment_rect,
offset: tile_offset,
edge_flags,
};
self.current_tile.x += 1;
Some(tile)
}
}
pub fn tiles(
prim_rect: &LayoutRect,
visible_rect: &LayoutRect,
image_rect: &DeviceIntRect,
device_tile_size: i32,
) -> TileIterator {
let visible_rect = match prim_rect.intersection(&visible_rect) {
Some(rect) => rect,
None => {
return TileIterator {
current_tile: TileOffset::zero(),
x: TileIteratorExtent {
tile_range: 0..0,
image_tiles: 0..0,
first_tile_layout_size: 0.0,
last_tile_layout_size: 0.0,
layout_tiling_origin: 0.0,
layout_prim_start: prim_rect.min.x,
},
y: TileIteratorExtent {
tile_range: 0..0,
image_tiles: 0..0,
first_tile_layout_size: 0.0,
last_tile_layout_size: 0.0,
layout_tiling_origin: 0.0,
layout_prim_start: prim_rect.min.y,
},
regular_tile_size: LayoutSize::zero(),
}
}
};
let layout_tile_size = LayoutSize::new(
device_tile_size as f32 / image_rect.width() as f32 * prim_rect.width(),
device_tile_size as f32 / image_rect.height() as f32 * prim_rect.height(),
);
let x_extent = tiles_1d(
layout_tile_size.width,
visible_rect.x_range(),
prim_rect.min.x,
image_rect.x_range(),
device_tile_size,
);
let y_extent = tiles_1d(
layout_tile_size.height,
visible_rect.y_range(),
prim_rect.min.y,
image_rect.y_range(),
device_tile_size,
);
TileIterator {
current_tile: point2(
x_extent.tile_range.start,
y_extent.tile_range.start,
),
x: x_extent,
y: y_extent,
regular_tile_size: layout_tile_size,
}
}
fn tiles_1d(
layout_tile_size: f32,
layout_visible_range: Range<f32>,
layout_prim_start: f32,
device_image_range: Range<i32>,
device_tile_size: i32,
) -> TileIteratorExtent {
debug_assert!(layout_tile_size > 0.0);
debug_assert!(layout_visible_range.end >= layout_visible_range.start);
debug_assert!(device_image_range.end > device_image_range.start);
debug_assert!(device_tile_size > 0);
let first_tile_device_size = first_tile_size_1d(&device_image_range, device_tile_size);
let last_tile_device_size = last_tile_size_1d(&device_image_range, device_tile_size);
let image_tiles = tile_range_1d(&device_image_range, device_tile_size);
let layout_offset = device_image_range.start as f32 * layout_tile_size / device_tile_size as f32;
let layout_tiling_origin = layout_prim_start - layout_offset;
let visible_tiles_start = f32::floor((layout_visible_range.start - layout_tiling_origin) / layout_tile_size) as i32;
let visible_tiles_end = f32::ceil((layout_visible_range.end - layout_tiling_origin) / layout_tile_size) as i32;
let mut tiles_start = i32::max(image_tiles.start, visible_tiles_start);
let tiles_end = i32::min(image_tiles.end, visible_tiles_end);
if tiles_start > tiles_end {
tiles_start = tiles_end;
}
let first_tile_layout_size = if tiles_start == image_tiles.start {
first_tile_device_size as f32 * layout_tile_size / device_tile_size as f32
} else {
layout_tile_size
};
let last_tile_layout_size = if tiles_end == image_tiles.end {
last_tile_device_size as f32 * layout_tile_size / device_tile_size as f32
} else {
layout_tile_size
};
TileIteratorExtent {
tile_range: tiles_start..tiles_end,
image_tiles,
first_tile_layout_size,
last_tile_layout_size,
layout_tiling_origin,
layout_prim_start,
}
}
fn tile_range_1d(
image_range: &Range<i32>,
regular_tile_size: i32,
) -> Range<i32> {
let mut start = image_range.start / regular_tile_size;
if image_range.start % regular_tile_size < 0 {
start -= 1;
}
let mut end = image_range.end / regular_tile_size;
if image_range.end % regular_tile_size > 0 {
end += 1;
}
start..end
}
fn first_tile_size_1d(
image_range: &Range<i32>,
regular_tile_size: i32,
) -> i32 {
let image_size = image_range.end - image_range.start;
i32::min(
match image_range.start % regular_tile_size {
0 => regular_tile_size,
m if m > 0 => regular_tile_size - m,
m => -m,
},
image_size
)
}
fn last_tile_size_1d(
image_range: &Range<i32>,
regular_tile_size: i32,
) -> i32 {
let image_size = image_range.end - image_range.start;
i32::min(
match image_range.end % regular_tile_size {
0 => regular_tile_size,
m if m < 0 => regular_tile_size + m,
m => m,
},
image_size,
)
}
pub fn compute_tile_rect(
image_rect: &DeviceIntRect,
regular_tile_size: TileSize,
tile: TileOffset,
) -> DeviceIntRect {
let regular_tile_size = regular_tile_size as i32;
DeviceIntRect::from_origin_and_size(
point2(
compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
),
size2(
compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
),
)
}
fn compute_tile_origin_1d(
img_range: Range<i32>,
regular_tile_size: i32,
tile_offset: i32,
) -> i32 {
let tile_range = tile_range_1d(&img_range, regular_tile_size);
if tile_offset == tile_range.start {
img_range.start
} else {
tile_offset * regular_tile_size
}
}
pub fn compute_tile_size(
image_rect: &DeviceIntRect,
regular_tile_size: TileSize,
tile: TileOffset,
) -> DeviceIntSize {
let regular_tile_size = regular_tile_size as i32;
size2(
compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
)
}
fn compute_tile_size_1d(
img_range: Range<i32>,
regular_tile_size: i32,
tile_offset: i32,
) -> i32 {
let tile_range = tile_range_1d(&img_range, regular_tile_size);
let actual_size = if tile_offset == tile_range.start {
first_tile_size_1d(&img_range, regular_tile_size)
} else if tile_offset == tile_range.end - 1 {
last_tile_size_1d(&img_range, regular_tile_size)
} else {
regular_tile_size
};
assert!(actual_size > 0);
actual_size
}
pub fn compute_tile_range(
visible_area: &DeviceIntRect,
tile_size: u16,
) -> TileRange {
let tile_size = tile_size as i32;
let x_range = tile_range_1d(&visible_area.x_range(), tile_size);
let y_range = tile_range_1d(&visible_area.y_range(), tile_size);
TileRange {
min: point2(x_range.start, y_range.start),
max: point2(x_range.end, y_range.end),
}
}
pub fn for_each_tile_in_range(
range: &TileRange,
mut callback: impl FnMut(TileOffset),
) {
for y in range.y_range() {
for x in range.x_range() {
callback(point2(x, y));
}
}
}
pub fn compute_valid_tiles_if_bounds_change(
prev_rect: &DeviceIntRect,
new_rect: &DeviceIntRect,
tile_size: u16,
) -> Option<TileRange> {
let intersection = match prev_rect.intersection(new_rect) {
Some(rect) => rect,
None => {
return Some(TileRange::zero());
}
};
let left = prev_rect.min.x != new_rect.min.x;
let right = prev_rect.max.x != new_rect.max.x;
let top = prev_rect.min.y != new_rect.min.y;
let bottom = prev_rect.max.y != new_rect.max.y;
if !left && !right && !top && !bottom {
return None;
}
let tw = 1.0 / (tile_size as f32);
let th = 1.0 / (tile_size as f32);
let tiles = intersection
.cast::<f32>()
.scale(tw, th);
let min_x = if left { f32::ceil(tiles.min.x) } else { f32::floor(tiles.min.x) };
let min_y = if top { f32::ceil(tiles.min.y) } else { f32::floor(tiles.min.y) };
let max_x = if right { f32::floor(tiles.max.x) } else { f32::ceil(tiles.max.x) };
let max_y = if bottom { f32::floor(tiles.max.y) } else { f32::ceil(tiles.max.y) };
Some(TileRange {
min: point2(min_x as i32, min_y as i32),
max: point2(max_x as i32, max_y as i32),
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use euclid::rect;
fn checked_for_each_tile(
prim_rect: &LayoutRect,
visible_rect: &LayoutRect,
device_image_rect: &DeviceIntRect,
device_tile_size: i32,
callback: &mut dyn FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask),
) {
let mut coverage = LayoutRect::zero();
let mut seen_tiles = HashSet::new();
for tile in tiles(
prim_rect,
visible_rect,
device_image_rect,
device_tile_size,
) {
assert!(!seen_tiles.contains(&tile.offset));
seen_tiles.insert(tile.offset);
coverage = coverage.union(&tile.rect);
assert!(prim_rect.contains_box(&tile.rect));
callback(&tile.rect, tile.offset, tile.edge_flags);
}
assert!(prim_rect.contains_box(&coverage));
assert!(coverage.contains_box(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero())));
}
#[test]
fn basic() {
let mut count = 0;
checked_for_each_tile(&rect(0., 0., 1000., 1000.).to_box2d(),
&rect(75., 75., 400., 400.).to_box2d(),
&rect(0, 0, 400, 400).to_box2d(),
36,
&mut |_tile_rect, _tile_offset, _tile_flags| {
count += 1;
},
);
assert_eq!(count, 36);
}
#[test]
fn empty() {
let mut count = 0;
checked_for_each_tile(&rect(0., 0., 74., 74.).to_box2d(),
&rect(75., 75., 400., 400.).to_box2d(),
&rect(0, 0, 400, 400).to_box2d(),
36,
&mut |_tile_rect, _tile_offset, _tile_flags| {
count += 1;
},
);
assert_eq!(count, 0);
}
#[test]
fn test_tiles_1d() {
let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 0..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..0, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 0);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..64, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
let result = tiles_1d(64.0, -100.0..10.0, 64.0, 64..310, 64);
assert_eq!(result.tile_range.start, result.tile_range.end);
let result = tiles_1d(64.0, 10.0..10000.0, -64.0, -64..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
let result = tiles_1d(64.0, -10000.0..-10.0, -64.0, -64..64, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 0);
assert_eq!(result.first_tile_layout_size, 64.0);
assert_eq!(result.last_tile_layout_size, 64.0);
let result = tiles_1d(128.0, -10000.0..10000.0, -64.0, -64..32, 64);
assert_eq!(result.tile_range.start, -1);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 128.0);
assert_eq!(result.last_tile_layout_size, 64.0);
let result = tiles_1d(10.0, 0.0..20.0, 0.0, 0..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 10.0);
assert_eq!(result.last_tile_layout_size, 10.0);
let result = tiles_1d(10.0, -20.0..0.0, -20.0, 0..64, 64);
assert_eq!(result.tile_range.start, 0);
assert_eq!(result.tile_range.end, 1);
assert_eq!(result.first_tile_layout_size, 10.0);
assert_eq!(result.last_tile_layout_size, 10.0);
}
#[test]
fn test_tile_range_1d() {
assert_eq!(tile_range_1d(&(0..256), 256), 0..1);
assert_eq!(tile_range_1d(&(0..257), 256), 0..2);
assert_eq!(tile_range_1d(&(-1..257), 256), -1..2);
assert_eq!(tile_range_1d(&(-256..256), 256), -1..1);
assert_eq!(tile_range_1d(&(-20..-10), 6), -4..-1);
assert_eq!(tile_range_1d(&(20..100), 256), 0..1);
}
#[test]
fn test_first_last_tile_size_1d() {
assert_eq!(first_tile_size_1d(&(0..10), 64), 10);
assert_eq!(first_tile_size_1d(&(-20..0), 64), 20);
assert_eq!(last_tile_size_1d(&(0..10), 64), 10);
assert_eq!(last_tile_size_1d(&(-20..0), 64), 20);
}
#[test]
fn doubly_partial_tiles() {
assert_eq!(first_tile_size_1d(&(300..310), 64), 10);
assert_eq!(first_tile_size_1d(&(-20..-10), 64), 10);
assert_eq!(last_tile_size_1d(&(300..310), 64), 10);
assert_eq!(last_tile_size_1d(&(-20..-10), 64), 10);
let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 300..310, 64);
assert_eq!(result.tile_range.start, 4);
assert_eq!(result.tile_range.end, 5);
assert_eq!(result.first_tile_layout_size, 10.0);
assert_eq!(result.last_tile_layout_size, 10.0);
}
#[test]
fn smaller_than_tile_size_at_origin() {
let r = compute_tile_rect(
&rect(0, 0, 80, 80).to_box2d(),
256,
point2(0, 0),
);
assert_eq!(r, rect(0, 0, 80, 80).to_box2d());
}
#[test]
fn smaller_than_tile_size_with_offset() {
let r = compute_tile_rect(
&rect(20, 20, 80, 80).to_box2d(),
256,
point2(0, 0),
);
assert_eq!(r, rect(20, 20, 80, 80).to_box2d());
}
}