#![allow(clippy::identity_op)]
use cairo::{Context, Format, ImageSurface};
#[cfg(feature = "png")]
use png::{ColorType, Encoder};
#[cfg(feature = "png")]
use std::fs::File;
#[cfg(feature = "png")]
use std::io::BufWriter;
use std::marker::PhantomData;
use std::path::Path;
#[cfg(feature = "png")]
use piet::util;
use piet::{ImageBuf, ImageFormat};
#[doc(hidden)]
pub use piet_cairo::*;
pub type Piet<'a> = CairoRenderContext<'a>;
pub type Brush = piet_cairo::Brush;
pub type PietText = CairoText;
pub type PietTextLayout = CairoTextLayout;
pub type PietTextLayoutBuilder = CairoTextLayoutBuilder;
pub type PietImage = CairoImage;
pub struct Device {
marker: std::marker::PhantomData<*const ()>,
}
unsafe impl Send for Device {}
pub struct BitmapTarget<'a> {
surface: ImageSurface,
cr: Context,
phantom: PhantomData<&'a ()>,
}
impl Device {
pub fn new() -> Result<Device, piet::Error> {
Ok(Device {
marker: std::marker::PhantomData,
})
}
pub fn bitmap_target(
&mut self,
width: usize,
height: usize,
pix_scale: f64,
) -> Result<BitmapTarget<'_>, piet::Error> {
let surface = ImageSurface::create(Format::ARgb32, width as i32, height as i32).unwrap();
let cr = Context::new(&surface).unwrap();
cr.scale(pix_scale, pix_scale);
let phantom = Default::default();
Ok(BitmapTarget {
surface,
cr,
phantom,
})
}
}
impl<'a> BitmapTarget<'a> {
pub fn render_context(&mut self) -> CairoRenderContext<'_> {
CairoRenderContext::new(&self.cr)
}
pub fn copy_raw_pixels(
&mut self,
fmt: ImageFormat,
buf: &mut [u8],
) -> Result<usize, piet::Error> {
if fmt != ImageFormat::RgbaPremul {
return Err(piet::Error::NotSupported);
}
self.surface.flush();
let stride = self.surface.stride() as usize;
let width = self.surface.width() as usize;
let height = self.surface.height() as usize;
let dst_len = width * height * 4;
let src_len = height.saturating_sub(1) * stride + width * 4;
if buf.len() < dst_len {
return Err(piet::Error::InvalidInput);
}
self.surface
.with_data(|src| {
debug_assert!(src.len() >= src_len);
debug_assert!(buf.len() >= dst_len);
unsafe {
for y in 0..height {
let src_off = y * stride;
let dst_off = y * width * 4;
for x in 0..width {
*buf.get_unchecked_mut(dst_off + x * 4 + 0) =
*src.get_unchecked(src_off + x * 4 + 2);
*buf.get_unchecked_mut(dst_off + x * 4 + 1) =
*src.get_unchecked(src_off + x * 4 + 1);
*buf.get_unchecked_mut(dst_off + x * 4 + 2) =
*src.get_unchecked(src_off + x * 4 + 0);
*buf.get_unchecked_mut(dst_off + x * 4 + 3) =
*src.get_unchecked(src_off + x * 4 + 3);
}
}
}
})
.map_err(|err| piet::Error::BackendError(Box::new(err)))?;
Ok(dst_len)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_image_buf(&mut self, fmt: ImageFormat) -> Result<ImageBuf, piet::Error> {
let width = self.surface.width() as usize;
let height = self.surface.height() as usize;
let mut buf = vec![0; width * height * 4];
self.copy_raw_pixels(fmt, &mut buf)?;
Ok(ImageBuf::from_raw(buf, fmt, width, height))
}
#[cfg(feature = "png")]
pub fn save_to_file<P: AsRef<Path>>(mut self, path: P) -> Result<(), piet::Error> {
let width = self.surface.width() as usize;
let height = self.surface.height() as usize;
let mut data = vec![0; width * height * 4];
self.copy_raw_pixels(ImageFormat::RgbaPremul, &mut data)?;
util::unpremultiply_rgba(&mut data);
let file = BufWriter::new(File::create(path).map_err(Into::<Box<_>>::into)?);
let mut encoder = Encoder::new(file, width as u32, height as u32);
encoder.set_color(ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
encoder
.write_header()
.map_err(Into::<Box<_>>::into)?
.write_image_data(&data)
.map_err(Into::<Box<_>>::into)?;
Ok(())
}
#[cfg(not(feature = "png"))]
pub fn save_to_file<P: AsRef<Path>>(self, _path: P) -> Result<(), piet::Error> {
Err(piet::Error::Unimplemented)
}
}