use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::io::{self, Write};
use log::debug;
use image::{Pixel, Rgb, DynamicImage, SubImage, GenericImageView};
use crate::{Vec2d, max_size_in_rect};
use crate::tile::Tile;
use crate::encoder::crop_tile;
use std::sync::Arc;
const BYTES_PER_PIXEL: usize = Rgb::<u8>::CHANNEL_COUNT as usize;
pub struct PixelStreamer<W: Write> {
strips: BTreeMap<usize, ImageStrip>,
writer: W,
size: Vec2d,
current_index: usize,
}
impl<W: Write> PixelStreamer<W> {
pub fn new(writer: W, size: Vec2d) -> Self {
PixelStreamer {
strips: BTreeMap::new(),
writer,
size,
current_index: 0,
}
}
pub fn add_tile(&mut self, tile: Tile) -> io::Result<()> {
for strip in ImageStrip::in_tile(tile, self.size) {
let key = strip.pixel_index(self.size);
self.strips.insert(key, strip);
}
self.advance(false)
}
fn advance(&mut self, finalize: bool) -> io::Result<()> {
while let Some(&start) = self.strips.keys().next() {
if start <= self.current_index {
let strip = self.strips.remove(&start).expect("The key should exist");
let strip_size = strip.size(self.size);
let start_strip_idx = self.current_index - start;
if start_strip_idx < strip_size {
strip.write_pixels(self.size, start_strip_idx, &mut self.writer)?;
debug!("Wrote a strip at position {} of size {}, skipping {} pixels",
self.current_index, strip_size, start_strip_idx);
self.current_index += strip_size - start_strip_idx;
}
} else if finalize {
self.fill_blank(start)?;
} else {
break;
}
}
Ok(())
}
pub fn finalize(&mut self) -> io::Result<()> {
self.advance(true)?;
let image_size = (self.size.x as usize) * (self.size.y as usize);
self.fill_blank(image_size)?;
self.writer.flush()?;
Ok(())
}
pub fn fill_blank(&mut self, until: usize) -> io::Result<()> {
if until > self.current_index {
let remaining = until - self.current_index;
debug!("Filling incomplete image with {} pixels", remaining);
let blank = vec![0; remaining * BYTES_PER_PIXEL];
self.writer.write_all(&blank)?;
self.current_index = until;
}
Ok(())
}
}
struct ImageStrip {
source: Arc<Tile>,
line: u32,
}
impl ImageStrip {
pub fn in_tile(tile: Tile, canvas_size: Vec2d) -> impl Iterator<Item=ImageStrip> {
let height = max_size_in_rect(tile.position, tile.size(), canvas_size).y;
std::iter::successors(Some(Arc::new(tile)), |s| Some(Arc::clone(s)))
.zip(0..height)
.map(|(source, line)| ImageStrip { source, line })
}
pub fn pixel_index(&self, image_size: Vec2d) -> usize {
let position = self.source.position + Vec2d { x: 0, y: self.line };
(position.y as usize) * (image_size.x as usize) + (position.x as usize)
}
pub fn cropped(&self, image_size: Vec2d) -> SubImage<&DynamicImage> {
crop_tile(&self.source, image_size)
}
pub fn size(&self, canvas_size: Vec2d) -> usize {
max_size_in_rect(self.source.position, self.source.size(), canvas_size).x as usize
}
pub fn write_pixels<W: Write>(&self, image_size: Vec2d, start_at: usize, writer: &mut W) -> io::Result<()> {
let img = self.cropped(image_size);
let x0 = u32::try_from(start_at).unwrap();
for x in x0..img.width() {
let rgb: Rgb<u8> = img.get_pixel(x, self.line).to_rgb();
writer.write_all(&rgb.0)?;
}
Ok(())
}
}
#[allow(clippy::zero_prefixed_literal)]
#[cfg(test)]
mod tests {
use image::{DynamicImage, ImageBuffer};
use super::*;
fn tiles(i: usize) -> Tile {
[
Tile {
position: Vec2d { x: 0, y: 0 },
image: DynamicImage::ImageRgb8(ImageBuffer::from_raw(2, 2, vec![
1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12,
]).unwrap()),
},
Tile {
position: Vec2d { x: 2, y: 0 },
image: DynamicImage::ImageRgb8(ImageBuffer::from_raw(2, 4, vec![
00, 00, 00, 10, 10, 10,
01, 01, 01, 11, 11, 11,
02, 02, 02, 12, 12, 12,
03, 03, 03, 13, 13, 13,
]).unwrap()),
},
Tile {
position: Vec2d { x: 0, y: 2 },
image: DynamicImage::ImageRgb8(ImageBuffer::from_raw(2, 2, vec![
100, 100, 100, 200, 200, 200,
200, 200, 200, 99, 99, 99,
]).unwrap()),
},
Tile {
position: Vec2d { x: 1, y: 0 },
image: DynamicImage::ImageRgb8(ImageBuffer::from_raw(2, 1, vec![
4, 5, 6, 00, 00, 00,
]).unwrap()),
}
][i].clone()
}
const WHOLE_IMAGE: &[u8] = &[
1, 2, 3, 4, 5, 6, 00, 00, 00, 10, 10, 10,
7, 8, 9, 10, 11, 12, 01, 01, 01, 11, 11, 11,
100, 100, 100, 200, 200, 200, 02, 02, 02, 12, 12, 12,
200, 200, 200, 99, 99, 99, 03, 03, 03, 13, 13, 13,
];
#[test]
fn tile0() {
assert_state_after_tiles(
&[0], vec![1, 2, 3, 4, 5, 6],
);
}
#[test]
fn tile1() {
assert_state_after_tiles(&[1], vec![]);
}
#[test]
fn tiles_0_and_1() {
assert_state_after_tiles(
&[0, 1], vec![
1, 2, 3, 4, 5, 6, 00, 00, 00, 10, 10, 10,
7, 8, 9, 10, 11, 12, 01, 01, 01, 11, 11, 11
],
);
}
#[test]
fn all_tiles() {
assert_state_after_tiles(
&[0, 1, 2], Vec::from(WHOLE_IMAGE),
);
}
#[test]
fn all_tiles_non_sorted() {
assert_state_after_tiles(&[1, 2, 0], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[2, 1, 0], Vec::from(WHOLE_IMAGE));
}
#[test]
fn all_tiles_overlapping_tiles() {
assert_state_after_tiles(&[0, 1, 0, 2], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[0, 0, 1, 1, 2, 2], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[2, 1, 2, 0], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[0, 1, 3, 2], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[0, 3, 1, 2], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[3, 0, 1, 2], Vec::from(WHOLE_IMAGE));
assert_state_after_tiles(&[0, 3, 0, 1, 2, 3], Vec::from(WHOLE_IMAGE));
}
fn assert_state_after_tiles(tile_indices: &[usize], expected: Vec<u8>) {
let mut out = vec![];
let mut streamer = PixelStreamer::new(&mut out, Vec2d { x: 4, y: 4 });
for &i in tile_indices {
streamer.add_tile(tiles(i)).unwrap();
}
assert_eq!(&out, &expected); }
#[test]
fn finalize_empty() {
let mut out = vec![];
let mut streamer = PixelStreamer::new(&mut out, Vec2d { x: 2, y: 2 });
streamer.finalize().unwrap();
assert_eq!(&out, &[ 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, ]
);
}
#[test]
fn finalize_only_tile2() {
let mut out = vec![];
let mut streamer = PixelStreamer::new(&mut out, Vec2d { x: 2, y: 5 });
streamer.add_tile(tiles(2)).unwrap();
streamer.finalize().unwrap();
assert_eq!(&out, &[ 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
100, 100, 100, 200, 200, 200,
200, 200, 200, 99, 99, 99,
0, 0, 0, 0, 0, 0
]
);
}
#[test]
fn tile_too_large() {
let mut out = vec![];
let mut streamer = PixelStreamer::new(&mut out, Vec2d { x: 1, y: 3 });
streamer.add_tile(tiles(2)).unwrap();
streamer.finalize().unwrap();
assert_eq!(&out, &[ 0, 0, 0,
0, 0, 0,
100, 100, 100,
]
);
}
}