use std::convert::TryInto;
mod glyphsets;
mod image;
mod int;
pub use self::{glyphsets::*, image::*};
pub type Span = u16;
type Span2 = u32;
const SPAN_BITS: usize = <Span as int::BinInteger>::BITS as usize;
pub type Fragment = u64;
#[derive(Clone)]
#[non_exhaustive]
pub struct Bmp2textOpts<'a> {
pub glyph_set: &'a dyn GlyphSet,
}
impl Default for Bmp2textOpts<'_> {
fn default() -> Self {
Self::new()
}
}
impl Bmp2textOpts<'_> {
pub fn new() -> Self {
Self {
glyph_set: GLYPH_SET_SLC,
}
}
}
#[derive(Default, Debug)]
pub struct Bmp2text {
row_group: Vec<Span>,
}
impl Bmp2text {
pub fn new() -> Self {
Self::default()
}
pub fn transform_and_write(
&mut self,
image: &impl ImageRead,
opts: &Bmp2textOpts,
out: &mut impl std::fmt::Write,
) -> std::fmt::Result {
use int::BinInteger;
let glyph_set = opts.glyph_set;
let mask_dims = glyph_set.mask_dims();
let mask_overlap = glyph_set.mask_overlap();
let [img_w, img_h] = image.dims();
let num_spans_per_line = (img_w + SPAN_BITS - 1) / SPAN_BITS;
let [out_w, out_h] = [
num_glyphs_for_image_width(img_w, opts),
num_lines_for_image_height(img_h, opts),
];
let num_spans_per_line_extra = num_spans_per_line + 1;
self.row_group
.resize(num_spans_per_line_extra * mask_dims[1], 0);
let mut row_group: Vec<&mut [Span]> = self
.row_group
.chunks_exact_mut(num_spans_per_line_extra)
.collect();
let row_group: &mut [&mut [Span]] = &mut row_group[..mask_dims[1]];
#[derive(Clone, Copy)]
struct RowState {
bits: Span2,
}
let mut row_states = [RowState { bits: 0 }; 16];
let row_states = &mut row_states[0..mask_dims[1]];
for out_y in 0..out_h {
for (y, row) in row_group.iter_mut().enumerate() {
image.copy_line_as_spans_to(out_y * (mask_dims[1] - mask_overlap[1]) + y, row);
}
let mut num_valid_bits = 0; let mut span_i = 0;
for _ in 0..out_w {
if num_valid_bits < mask_dims[0] {
for (row_state, row) in row_states.iter_mut().zip(row_group.iter_mut()) {
row_state.bits |= (row[span_i] as Span2) << num_valid_bits;
}
span_i += 1;
num_valid_bits += SPAN_BITS;
}
let mut fragment: Fragment = 0;
for (i, row_state) in row_states.iter_mut().enumerate() {
fragment |= (row_state.bits as Fragment & Fragment::ones(0..mask_dims[0] as _))
<< (i * mask_dims[0]);
row_state.bits >>= mask_dims[0] - mask_overlap[0];
}
num_valid_bits -= mask_dims[0] - mask_overlap[0];
debug_assert!(fragment < (1 << (mask_dims[0] * mask_dims[1])));
let glyph = glyph_set.fragment_to_glyph(fragment);
out.write_str(glyph)?;
}
out.write_str("\n")?;
}
Ok(())
}
}
pub fn num_glyphs_for_image_width(width: usize, opts: &Bmp2textOpts) -> usize {
let mask_dims = opts.glyph_set.mask_dims();
let mask_overlap = opts.glyph_set.mask_overlap();
width.saturating_sub(mask_overlap[0]) / (mask_dims[0] - mask_overlap[0])
}
pub fn num_lines_for_image_height(height: usize, opts: &Bmp2textOpts) -> usize {
let mask_dims = opts.glyph_set.mask_dims();
let mask_overlap = opts.glyph_set.mask_overlap();
height.saturating_sub(mask_overlap[1]) / (mask_dims[1] - mask_overlap[1])
}
pub fn max_output_len_for_image_dims(
[width, height]: [usize; 2],
opts: &Bmp2textOpts,
) -> Option<usize> {
let glyph_set = opts.glyph_set;
num_glyphs_for_image_width(width, opts)
.checked_mul(glyph_set.max_glyph_len())
.and_then(|x| x.checked_add(1)) .and_then(|x| x.checked_mul(num_lines_for_image_height(height, opts)))
}
#[doc(hidden)]
#[cfg(feature = "log")]
pub fn adjust_image_size_for_output_size_preserving_aspect_ratio(
image_dims: [usize; 2],
output_dims: [usize; 2],
can_scale_up: bool,
cover: bool,
cell_width: f64,
opts: &Bmp2textOpts,
) -> Option<[usize; 2]> {
let mask_dims = opts.glyph_set.mask_dims();
let mask_overlap = opts.glyph_set.mask_overlap();
let [nat_out_w, nat_out_h] = [
num_glyphs_for_image_width(image_dims[0], opts),
num_lines_for_image_height(image_dims[1], opts),
];
let aspect = (mask_dims[1] - mask_overlap[1]) as f64 / (mask_dims[0] - mask_overlap[0]) as f64
* cell_width;
let [img_w, img_h] = [
nat_out_w as f64 / aspect.max(1.0),
nat_out_h as f64 * aspect.min(1.0),
];
log::debug!("'natural' output size = {:?}", [img_w, img_h]);
let scale_x = output_dims[0] as f64 / img_w;
let scale_y = output_dims[1] as f64 / img_h;
let mut scale = if cover {
f64::max(scale_x, scale_y)
} else {
f64::min(scale_x, scale_y)
};
if !can_scale_up {
scale = scale.min(1.0);
}
log::debug!("scaling the 'natural' output size by {}...", scale);
let output_dims = [
(img_w * scale).round() as usize,
(img_h * scale).round() as usize,
];
adjust_image_size_for_output_size(output_dims, opts)
}
#[doc(hidden)]
pub fn adjust_image_size_for_output_size(
output_dims: [usize; 2],
opts: &Bmp2textOpts,
) -> Option<[usize; 2]> {
let mask_dims = opts.glyph_set.mask_dims();
let mask_overlap = opts.glyph_set.mask_overlap();
Some([
output_dims[0]
.checked_mul(mask_dims[0] - mask_overlap[0])?
.checked_add(mask_overlap[0])?
.try_into()
.ok()?,
output_dims[1]
.checked_mul(mask_dims[1] - mask_overlap[1])?
.checked_add(mask_overlap[1])?
.try_into()
.ok()?,
])
}