1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use image::{GenericImage, RgbImage};
use log::info;
use serde::{Deserialize, Serialize};
use crate::error::{MosaicError, PhomoError};
use crate::solvers::hungarian::Hungarian;
use crate::solvers::{Solve, SolverConfig};
use crate::{DistanceMatrix, Mosaic};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Blueprint {
pub cells: Vec<Cell>,
pub cell_width: u32,
pub cell_height: u32,
pub grid_width: u32,
pub grid_height: u32,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Cell {
pub tile_index: usize,
pub x: u32,
pub y: u32,
}
/// A serializable struct which represents a [crate::mosaic::Mosaic] that has yet to be rendered.
impl Blueprint {
/// Render the [Blueprint].
///
/// Errors:
/// - [`PhomoError::MosaicError`]: An error occurred while rendering the mosaic.
/// - [`PhomoError::ImageError`]: An error occurred while copying the tiles to the mosaic image.
pub fn render(
&self,
master_img: &RgbImage,
tiles: &[RgbImage],
) -> Result<RgbImage, PhomoError> {
let mut mosaic_img = RgbImage::new(master_img.width(), master_img.height());
info!(
"Building mosaic, size: {}x{}, cell size: {}x{}, grid size: {}x{}",
mosaic_img.width(),
mosaic_img.height(),
self.cell_width,
self.cell_height,
self.grid_width,
self.grid_height
);
for cell in self.cells.iter() {
let tile = tiles
.get(cell.tile_index)
.ok_or(MosaicError::InvalidTileIndex(cell.tile_index))?;
mosaic_img.copy_from(tile, cell.x, cell.y)?;
}
Ok(mosaic_img)
}
}
impl Mosaic {
/// Compute the tile to master cell assignments, and construct a [`Blueprint`] of the mosaic
/// image.
///
/// # Errors
/// - [`PhomoError::MosaicError`]: An error occurred while building the mosaic blueprint.
/// - [`PhomoError::SolverError`]: An error occurred while solving the tile to cell assignments.
pub fn build_blueprint(
&self,
distance_matrix: DistanceMatrix,
config: SolverConfig,
) -> Result<Blueprint, PhomoError> {
let solver = Hungarian::new(config);
self.build_blueprint_with_solver(distance_matrix, solver)
}
/// Compute the tile to master cell assignments using the provided solver algorithm, and
/// construct a [`Blueprint`] of the mosaic image. See [`phomo::solvers`](crate::solvers) for
/// implemented solvers.
///
/// # Arguments:
/// - `distance_matrix`: The distance matrix between the master image and the tiles.
/// - `solver`: The solver algorithm to use for the assignment problem.
///
/// # Errors
/// - [`PhomoError::MosaicError`]: An error occurred while building the mosaic.
/// - [`PhomoError::SolverError`]: An error occurred while solving the tile to cell assignments.
pub fn build_blueprint_with_solver<S: Solve>(
&self,
distance_matrix: DistanceMatrix,
mut solver: S,
) -> Result<Blueprint, PhomoError> {
self.check_distance_matrix(&distance_matrix)?;
let assignments = distance_matrix.assignments(&mut solver)?;
let (grid_width, grid_height) = self.grid_size;
let (cell_width, cell_height) = self.master.cell_size;
info!(
"Building mosaic blueprint, size: {}x{}, cell size: {}x{}, grid size: {}x{}",
cell_width * grid_width,
cell_height * grid_height,
cell_width,
cell_height,
grid_width,
grid_height
);
let cells = assignments
.into_iter()
.enumerate()
.map(|(cell_idx, tile_idx)| {
let x = (cell_idx as u32 % grid_width) * cell_width;
let y = (cell_idx as u32 / grid_width) * cell_height;
Cell {
tile_index: tile_idx % self.tiles.len(),
x,
y,
}
})
.collect::<Vec<_>>();
Ok(Blueprint {
cells,
cell_width,
cell_height,
grid_width,
grid_height,
})
}
}