use std::io::Cursor;
use image::{
ColorType, ImageDecoder, ImageDecoderRect, ImageResult,
error::{DecodingError, ImageError, ImageFormatHint},
};
use crate::djvu_document::DjVuPage;
use crate::djvu_render::{RenderError, RenderOptions};
#[derive(Debug, thiserror::Error)]
pub enum ImageCompatError {
#[error("render error: {0}")]
Render(#[from] RenderError),
}
impl From<ImageCompatError> for ImageError {
fn from(e: ImageCompatError) -> Self {
ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name("DjVu".to_string()),
e,
))
}
}
pub struct DjVuDecoder<'a> {
page: &'a DjVuPage,
width: u32,
height: u32,
}
impl<'a> DjVuDecoder<'a> {
pub fn new(page: &'a DjVuPage) -> Result<Self, ImageCompatError> {
Ok(Self {
width: page.width() as u32,
height: page.height() as u32,
page,
})
}
#[must_use]
pub fn with_size(mut self, width: u32, height: u32) -> Self {
self.width = width;
self.height = height;
self
}
fn render_to_vec(&self) -> Result<Vec<u8>, ImageCompatError> {
let opts = RenderOptions {
width: self.width,
height: self.height,
scale: self.width as f32 / self.page.width().max(1) as f32,
..RenderOptions::default()
};
let size = (self.width as usize)
.saturating_mul(self.height as usize)
.saturating_mul(4);
let mut buf = vec![0u8; size];
self.page.render_into(&opts, &mut buf)?;
Ok(buf)
}
}
impl<'a> ImageDecoder<'a> for DjVuDecoder<'a> {
type Reader = Cursor<Vec<u8>>;
fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
fn color_type(&self) -> ColorType {
ColorType::Rgba8
}
#[allow(deprecated)]
fn into_reader(self) -> ImageResult<Self::Reader> {
let data = self.render_to_vec().map_err(ImageError::from)?;
Ok(Cursor::new(data))
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
let data = self.render_to_vec().map_err(ImageError::from)?;
if buf.len() != data.len() {
return Err(ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name("DjVu".to_string()),
format!(
"buffer size mismatch: expected {}, got {}",
data.len(),
buf.len()
),
)));
}
buf.copy_from_slice(&data);
Ok(())
}
}
impl<'a> ImageDecoderRect<'a> for DjVuDecoder<'a> {
#[allow(deprecated)]
fn read_rect_with_progress<F: Fn(image::Progress)>(
&mut self,
x: u32,
y: u32,
width: u32,
height: u32,
buf: &mut [u8],
_progress_callback: F,
) -> ImageResult<()> {
let bytes_per_pixel = self.color_type().bytes_per_pixel() as usize;
let row_stride = self.width as usize * bytes_per_pixel;
let rect_row_bytes = width as usize * bytes_per_pixel;
let x_end = x.checked_add(width).ok_or_else(|| {
ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name("DjVu".to_string()),
"rectangle x+width overflows u32",
))
})?;
let y_end = y.checked_add(height).ok_or_else(|| {
ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name("DjVu".to_string()),
"rectangle y+height overflows u32",
))
})?;
if x_end > self.width || y_end > self.height {
return Err(ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name("DjVu".to_string()),
format!(
"rectangle ({x},{y},{width},{height}) out of image bounds ({}×{})",
self.width, self.height
),
)));
}
let full = self.render_to_vec().map_err(ImageError::from)?;
for row in 0..height as usize {
let src_y = y as usize + row;
let src_start = src_y * row_stride + x as usize * bytes_per_pixel;
let src_end = src_start + rect_row_bytes;
let dst_start = row * rect_row_bytes;
let dst_end = dst_start + rect_row_bytes;
buf[dst_start..dst_end].copy_from_slice(&full[src_start..src_end]);
}
Ok(())
}
}