use skia_safe::{
AlphaType, ColorSpace as SkColorSpace, ColorType, IPoint, ISize, ImageInfo,
Pixmap, Surface as SkSurface,
};
use crate::{
backend::{EngineKind, engine_kind_from, resolve_engine},
color::LinearColorSpace,
context::page::ExportOptions,
error::Error,
image::Image,
pixels::{
ExportedPixels, PixelColorSpace, PixelDepth, PixelExportOptions,
SurfaceOptions,
},
recorder::Canvas,
};
pub struct Surface {
inner: SkSurface,
color_space: LinearColorSpace,
working_color_space: SkColorSpace,
engine: EngineKind,
width: u32,
height: u32,
}
impl Surface {
pub(crate) fn new(
width: u32,
height: u32,
options: SurfaceOptions,
) -> Result<Self, Error> {
if width == 0 || height == 0 {
return Err(Error::InvalidDimensions {
width: width as f32,
height: height as f32,
});
}
let cs = options.color_space.to_skia_color_space()?;
let info = ImageInfo::new(
(width as i32, height as i32),
ColorType::RGBAF16,
AlphaType::Premul,
cs.clone(),
);
let internal = resolve_engine(options.engine)?;
let export_options = ExportOptions {
msaa: options.msaa,
color_type: ColorType::RGBAF16,
color_space: cs.clone(),
..ExportOptions::default()
};
let surface = internal.make_surface(&info, &export_options).map_err(
|reason| Error::SurfaceCreate {
reason: format!(
"could not allocate {width}x{height} surface: {reason}"
),
},
)?;
Ok(Self {
inner: surface,
color_space: options.color_space,
working_color_space: cs,
engine: engine_kind_from(internal),
width,
height,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn color_space(&self) -> LinearColorSpace {
self.color_space
}
pub fn engine(&self) -> EngineKind {
self.engine
}
pub fn flush(&mut self) {
#[cfg(any(feature = "vulkan", feature = "metal"))]
if self.engine == EngineKind::Gpu {
crate::gpu::RenderingEngine::GPU.with_direct_context(|ctx| {
if let Some(ctx) = ctx {
ctx.flush_and_submit();
}
});
}
}
pub fn snapshot(&mut self) -> Image {
Image {
inner: self.inner.image_snapshot(),
}
}
pub fn create_offscreen(
&mut self,
width: u32,
height: u32,
) -> Result<Surface, Error> {
if width == 0 || height == 0 {
return Err(Error::InvalidDimensions {
width: width as f32,
height: height as f32,
});
}
let off = self
.inner
.new_surface_with_dimensions(ISize::new(
width as i32,
height as i32,
))
.ok_or_else(|| Error::SurfaceCreate {
reason: format!(
"could not allocate {width}x{height} offscreen surface"
),
})?;
Ok(Surface {
inner: off,
color_space: self.color_space,
working_color_space: self.working_color_space.clone(),
engine: self.engine,
width,
height,
})
}
pub fn with_canvas<R>(
&mut self,
f: impl FnOnce(&mut Canvas<'_>) -> R,
) -> R {
let working_cs = self.working_color_space.clone();
let canvas = self.inner.canvas();
let mut nc = Canvas::new(canvas, working_cs);
f(&mut nc)
}
pub fn read_pixels(&mut self) -> Result<ExportedPixels, Error> {
self.read_pixels_as(PixelExportOptions::default())
}
pub fn read_pixels_raw(&mut self) -> Result<ExportedPixels, Error> {
self.read_pixels_as(PixelExportOptions {
color_space: self.linear_pixel_color_space(),
depth: PixelDepth::F16,
premultiplied: true,
})
}
pub fn read_pixels_linear(&mut self) -> Result<ExportedPixels, Error> {
self.read_pixels_as(PixelExportOptions {
color_space: self.linear_pixel_color_space(),
depth: PixelDepth::F32,
premultiplied: true,
})
}
pub fn read_pixels_as(
&mut self,
options: PixelExportOptions,
) -> Result<ExportedPixels, Error> {
let dst_cs = options.color_space.to_skia_color_space()?;
let dst_ct = options.depth.to_skia_color_type();
let dst_at = if options.premultiplied {
AlphaType::Premul
} else {
AlphaType::Unpremul
};
let info = ImageInfo::new(
(self.width as i32, self.height as i32),
dst_ct,
dst_at,
dst_cs,
);
let bpp = options.depth.bytes_per_pixel();
let stride = (self.width as usize) * bpp;
let mut buffer: Vec<u8> = vec![0; stride * self.height as usize];
if !self.inner.read_pixels(
&info,
&mut buffer,
stride,
IPoint::new(0, 0),
) {
return Err(Error::PixelReadback {
reason: format!(
"read failed for {:?} {:?} premul={}",
options.color_space, options.depth, options.premultiplied
),
});
}
Ok(ExportedPixels::new(
self.width,
self.height,
stride,
options.color_space,
options.depth,
options.premultiplied,
buffer,
))
}
pub fn write_pixels(
&mut self,
bytes: &[u8],
options: PixelExportOptions,
) -> Result<(), Error> {
let dst_cs = options.color_space.to_skia_color_space()?;
let dst_ct = options.depth.to_skia_color_type();
let dst_at = if options.premultiplied {
AlphaType::Premul
} else {
AlphaType::Unpremul
};
let info = ImageInfo::new(
(self.width as i32, self.height as i32),
dst_ct,
dst_at,
dst_cs,
);
let bpp = options.depth.bytes_per_pixel();
let stride = (self.width as usize) * bpp;
let expected = stride * self.height as usize;
if bytes.len() != expected {
return Err(Error::InvalidByteLength {
expected,
actual: bytes.len(),
});
}
let mut copy = bytes.to_vec();
let pixmap =
Pixmap::new(&info, &mut copy, stride).ok_or_else(|| {
Error::PixelWrite {
reason: "pixmap construct failed".to_string(),
}
})?;
self.inner
.write_pixels_from_pixmap(&pixmap, IPoint::new(0, 0));
Ok(())
}
pub fn write_pixels_linear(&mut self, bytes: &[u8]) -> Result<(), Error> {
self.write_pixels(
bytes,
PixelExportOptions {
color_space: self.linear_pixel_color_space(),
depth: PixelDepth::F32,
premultiplied: true,
},
)
}
fn linear_pixel_color_space(&self) -> PixelColorSpace {
match self.color_space {
LinearColorSpace::Srgb => PixelColorSpace::SrgbLinear,
LinearColorSpace::DisplayP3 => PixelColorSpace::DisplayP3Linear,
LinearColorSpace::Rec2020 => PixelColorSpace::Rec2020Linear,
}
}
}