-
-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathoutput_image.rs
More file actions
162 lines (137 loc) · 5.39 KB
/
output_image.rs
File metadata and controls
162 lines (137 loc) · 5.39 KB
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use std::{convert::TryFrom, marker::PhantomData, path::Path};
use base64::Engine;
use embedded_graphics::{
pixelcolor::{raw::ToBytes, Gray8, Rgb888, RgbColor},
prelude::*,
primitives::Rectangle,
};
use embedded_graphics::primitives::Circle;
use image::{
codecs::png::{CompressionType, FilterType, PngEncoder},
ImageBuffer, ImageEncoder, Luma, Rgb,
};
use crate::{display::SimulatorDisplay, output_settings::OutputSettings};
use crate::output_settings::PixelShape;
/// Output image.
///
/// An output image is the result of applying [`OutputSettings`] to a [`SimulatorDisplay`]. It can
/// be used to save a simulator display to a PNG file.
///
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct OutputImage<C> {
size: Size,
pub(crate) data: Box<[u8]>,
pub(crate) output_settings: OutputSettings,
color_type: PhantomData<C>,
}
impl<C> OutputImage<C>
where
C: PixelColor + From<Rgb888> + ToBytes,
<C as ToBytes>::Bytes: AsRef<[u8]>,
{
/// Creates a new output image.
pub(crate) fn new<DisplayC>(
display: &SimulatorDisplay<DisplayC>,
output_settings: &OutputSettings,
) -> Self
where
DisplayC: PixelColor + Into<Rgb888>,
{
let size = output_settings.framebuffer_size(display);
// Create an empty pixel buffer, filled with the background color.
let background_color = C::from(output_settings.theme.convert(Rgb888::BLACK)).to_be_bytes();
let data = background_color
.as_ref()
.iter()
.copied()
.cycle()
.take(size.width as usize * size.height as usize * background_color.as_ref().len())
.collect::<Vec<_>>()
.into_boxed_slice();
Self {
size,
data,
output_settings: output_settings.clone(),
color_type: PhantomData,
}
}
/// Updates the image from a [`SimulatorDisplay`].
pub fn update<DisplayC>(&mut self, display: &SimulatorDisplay<DisplayC>)
where
DisplayC: PixelColor + Into<Rgb888>,
{
let pixel_pitch = (self.output_settings.scale + self.output_settings.pixel_spacing) as i32;
let pixel_size = Size::new(self.output_settings.scale, self.output_settings.scale);
for p in display.bounding_box().points() {
let raw_color = display.get_pixel(p).into();
let themed_color = self.output_settings.theme.convert(raw_color);
let output_color = C::from(themed_color).to_be_bytes();
let output_color = output_color.as_ref();
match self.output_settings.pixel_shape {
PixelShape::Square => {
for p in Rectangle::new(p * pixel_pitch, pixel_size).points() {
if let Ok((x, y)) = <(u32, u32)>::try_from(p) {
let start_index = (x + y * self.size.width) as usize * output_color.len();
self.data[start_index..start_index + output_color.len()]
.copy_from_slice(output_color)
}
}
}
PixelShape::Dot => {
for p in Circle::new(p * pixel_pitch + Point::new(pixel_pitch / 2, pixel_pitch / 2), self.output_settings.scale/ 2).points() {
if let Ok((x, y)) = <(u32, u32)>::try_from(p) {
let start_index = (x + y * self.size.width) as usize * output_color.len();
self.data[start_index..start_index + output_color.len()]
.copy_from_slice(output_color)
}
}
}
}
}
}
}
impl<C: OutputImageColor> OutputImage<C> {
/// Saves the image content to a PNG file.
pub fn save_png<PATH: AsRef<Path>>(&self, path: PATH) -> image::ImageResult<()> {
let png = self.encode_png()?;
std::fs::write(path, png)?;
Ok(())
}
/// Returns the image as a base64 encoded PNG.
pub fn to_base64_png(&self) -> image::ImageResult<String> {
let png = self.encode_png()?;
Ok(base64::engine::general_purpose::STANDARD.encode(png))
}
fn encode_png(&self) -> image::ImageResult<Vec<u8>> {
let mut png = Vec::new();
PngEncoder::new_with_quality(&mut png, CompressionType::Best, FilterType::default())
.write_image(
self.data.as_ref(),
self.size.width,
self.size.height,
C::IMAGE_COLOR_TYPE.into(),
)?;
Ok(png)
}
/// Returns the output image as an [`image`] crate [`ImageBuffer`].
pub fn as_image_buffer(&self) -> ImageBuffer<C::ImageColor, &[u8]> {
ImageBuffer::from_raw(self.size.width, self.size.height, self.data.as_ref()).unwrap()
}
}
impl<C> OriginDimensions for OutputImage<C> {
fn size(&self) -> Size {
self.size
}
}
pub trait OutputImageColor {
type ImageColor: image::Pixel<Subpixel = u8> + 'static;
const IMAGE_COLOR_TYPE: image::ColorType;
}
impl OutputImageColor for Gray8 {
type ImageColor = Luma<u8>;
const IMAGE_COLOR_TYPE: image::ColorType = image::ColorType::L8;
}
impl OutputImageColor for Rgb888 {
type ImageColor = Rgb<u8>;
const IMAGE_COLOR_TYPE: image::ColorType = image::ColorType::Rgb8;
}