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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
use crate::geometry::{tile::ToRgb, Map, Point};
use rand::Rng;
use std::time::Duration;
/// How each tile gets rendered.
#[derive(Debug, Clone, Copy)]
pub enum Style {
/// Fill the entire 4x4 area with the tile's color.
Fill,
/// Fill a 3x3 area with the tile's color, leaving a 1 pixel black grid pattern between.
Grid,
/// Fill a 3x3 cross with the tile's color, leaving black space between.
Cross,
/// Fill a 3x3 cross with the tile's color, plus up to 4 more chosen randomly,
/// for a sparkling effect.
SparkleCross,
}
impl Style {
fn offsets(self) -> Box<dyn Iterator<Item = Point>> {
match self {
Style::Cross => Box::new(std::array::IntoIter::new([
Point::new(1, 0),
Point::new(0, 1),
Point::new(1, 1),
Point::new(2, 1),
Point::new(1, 2),
])),
Style::SparkleCross => {
let mut rng = rand::thread_rng();
let corners = std::array::IntoIter::new([
Point::new(0, 0),
Point::new(0, 2),
Point::new(2, 0),
Point::new(2, 2),
])
.filter(move |_point| rng.gen());
Box::new(Style::Cross.offsets().chain(corners))
}
Style::Grid => Box::new((0..3).flat_map(|y| (0..3).map(move |x| Point::new(x, y)))),
Style::Fill => Box::new((0..4).flat_map(|y| (0..4).map(move |x| Point::new(x, y)))),
}
}
}
pub fn render_point<Tile: ToRgb>(
position: Point,
tile: &Tile,
subpixels: &mut [u8],
width: usize,
style: Style,
) {
let x = |point: Point| point.x as usize;
let y = |point: Point| point.y as usize;
let row_pixels = pixel_size(width) as usize;
// the linear index of a position has the following components:
//
// - 2: offset from left edge
// - 2 * row_pixels: offset from top
// - x(position) * 4: x component of position
// - y(position) * 4 * row_pixels: y component of position
// - x(offset): x offset
// - y(offset) * row_pixels: y offset
//
// It is multiplied by 3, because that is how many bytes each pixel takes
//
// Note: this requires that the offset be in the positive quadrant
let linear_idx = |offset: Point| {
(2 + (2 * row_pixels)
+ (x(position) * 4)
+ (y(position) * 4 * row_pixels)
+ x(offset)
+ (y(offset) * row_pixels))
* 3
};
let rgb = tile.to_rgb();
for offset in style.offsets() {
let idx = linear_idx(offset);
subpixels[idx..idx + 3].copy_from_slice(&rgb);
}
}
/// Each tile is 4px high and wide, with a 2px margin on the outside edges of the image.
pub fn pixel_size(width: usize) -> u16 {
((width + 1) * 4) as u16
}
/// Total pixels in an image for a map
///
/// Each tile is 4px high and 4px wide, with a 2px margin on the outside
/// edges of the image.
pub fn n_pixels_for(width: usize, height: usize) -> usize {
pixel_size(width) as usize * pixel_size(height) as usize
}
pub type Encoder = gif::Encoder<std::io::BufWriter<std::fs::File>>;
pub type EncodingError = gif::EncodingError;
/// An `Animation` holds a handle to an unfinished gif animation.
///
/// _Depends on the `map-render` feature._
///
/// It is created with [`Map::prepare_animation`].
///
/// The gif is finalized when this struct is dropped.
pub struct Animation {
encoder: Encoder,
style: Style,
}
impl Animation {
/// Create a new animation from an encoder and frame duration.
///
/// This animation will repeat infinitely, displaying each frame for
/// `frame_duration`.
pub(crate) fn new(
mut encoder: Encoder,
frame_duration: Duration,
style: Style,
) -> Result<Animation, EncodingError> {
encoder.set_repeat(gif::Repeat::Infinite)?;
// delay is set in hundredths of a second
encoder.write_extension(gif::ExtensionData::new_control_ext(
(frame_duration.as_millis() / 10) as u16,
gif::DisposalMethod::Any,
false,
None,
))?;
Ok(Animation { encoder, style })
}
/// Write a frame to this animation.
///
/// This frame will be visible for the duration specified at the animation's
/// creation.
pub fn write_frame<Tile: ToRgb>(&mut self, map: &Map<Tile>) -> Result<(), EncodingError> {
self.encoder.write_frame(&map.render_frame(self.style))
}
}