1use std::path::PathBuf;
2
3#[cfg(feature = "vulkan-backend")]
4use std::sync::Arc;
5
6#[cfg(feature = "vulkan-backend")]
7use ash::vk::Handle;
8#[cfg(feature = "vulkan-backend")]
9use skia_safe::gpu::{self, direct_contexts, vk as skia_vk};
10use skia_safe::{
11 AlphaType, ColorType, FontMgr, IPoint, ImageInfo, Size, jpeg_encoder, png_encoder,
12 resources::NativeResourceProvider, surfaces, svg::Dom, webp_encoder,
13};
14
15#[cfg(feature = "vulkan-backend")]
16use crate::VulkanState;
17use crate::{
18 CachedResourceProvider, ImageData, JpegOptions, RenderOptions, SvgRenderError, WebpOptions,
19 options::MAX_RENDER_BYTES,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum RenderBackend {
25 Cpu,
27 Vulkan,
29}
30
31pub struct CpuSvgRenderer {
37 resource_provider: CachedResourceProvider,
38 readback_buffer: Vec<u8>,
40}
41
42impl CpuSvgRenderer {
43 pub fn new() -> Result<Self, SvgRenderError> {
45 Ok(Self {
46 resource_provider: CachedResourceProvider::new(FontMgr::default()),
47 readback_buffer: Vec::new(),
48 })
49 }
50
51 pub fn add_resource_search_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
56 self.resource_provider.add_search_dir(dir);
57 self
58 }
59
60 pub fn set_resource_search_dirs<I, P>(&mut self, dirs: I) -> &mut Self
62 where
63 I: IntoIterator<Item = P>,
64 P: Into<PathBuf>,
65 {
66 self.resource_provider.set_search_dirs(dirs);
67 self
68 }
69
70 pub fn render_svg(
72 &mut self,
73 svg: impl AsRef<[u8]>,
74 options: &RenderOptions,
75 ) -> Result<ImageData, SvgRenderError> {
76 let mut surface = self.render_surface(svg, options)?;
77 read_surface_pixels(&mut surface, options, &mut self.readback_buffer)
78 }
79
80 pub fn render_svg_to_png(
82 &mut self,
83 svg: impl AsRef<[u8]>,
84 options: &RenderOptions,
85 ) -> Result<Vec<u8>, SvgRenderError> {
86 let mut surface = self.render_surface(svg, options)?;
87 let image = surface.image_snapshot();
88 let data = png_encoder::encode_image(None, &image, &png_encoder::Options::default())
89 .ok_or(SvgRenderError::PngEncode)?;
90
91 Ok(data.as_bytes().to_vec())
92 }
93
94 pub fn render_svg_to_jpeg(
96 &mut self,
97 svg: impl AsRef<[u8]>,
98 options: &RenderOptions,
99 jpeg_options: JpegOptions,
100 ) -> Result<Vec<u8>, SvgRenderError> {
101 let mut surface = self.render_surface(svg, options)?;
102 let image = surface.image_snapshot();
103 let data = jpeg_encoder::encode_image(None, &image, &jpeg_options.into())
104 .ok_or(SvgRenderError::JpegEncode)?;
105
106 Ok(data.as_bytes().to_vec())
107 }
108
109 pub fn render_svg_to_webp(
111 &mut self,
112 svg: impl AsRef<[u8]>,
113 options: &RenderOptions,
114 webp_options: WebpOptions,
115 ) -> Result<Vec<u8>, SvgRenderError> {
116 let mut surface = self.render_surface(svg, options)?;
117 let image = surface.image_snapshot();
118 let data = webp_encoder::encode_image(None, &image, &webp_options.into())
119 .ok_or(SvgRenderError::WebpEncode)?;
120
121 Ok(data.as_bytes().to_vec())
122 }
123
124 fn render_surface(
126 &mut self,
127 svg: impl AsRef<[u8]>,
128 options: &RenderOptions,
129 ) -> Result<skia_safe::Surface, SvgRenderError> {
130 let (width, height) = options.size.as_i32_pair();
131 let info = rgba_image_info(width, height);
132 let mut surface =
133 surfaces::raster(&info, None, None).ok_or(SvgRenderError::RenderTarget)?;
134 render_dom(surface.canvas(), svg, options, &self.resource_provider)?;
135 Ok(surface)
136 }
137}
138
139#[cfg(feature = "vulkan-backend")]
144pub struct VulkanSvgRenderer {
145 vulkan: Arc<VulkanState>,
146 context: gpu::DirectContext,
147 resource_provider: CachedResourceProvider,
148 readback_buffer: Vec<u8>,
149}
150
151#[cfg(feature = "vulkan-backend")]
152impl VulkanSvgRenderer {
153 pub fn new() -> Result<Self, SvgRenderError> {
157 let vulkan = Arc::new(VulkanState::new()?);
158 let get_proc_vulkan = Arc::clone(&vulkan);
159 let get_proc = move |proc| get_proc_vulkan.get_proc(proc);
160
161 let backend_context = unsafe {
162 skia_vk::BackendContext::new_builder(
163 vulkan.instance.handle().as_raw() as skia_vk::Instance,
164 vulkan.physical_device.as_raw() as skia_vk::PhysicalDevice,
165 vulkan.device.handle().as_raw() as skia_vk::Device,
166 (
167 vulkan.queue.as_raw() as skia_vk::Queue,
168 vulkan.queue_family_index as usize,
169 ),
170 &get_proc,
171 Some(skia_vk::Version::new(1, 1, 0)),
172 )
173 .build()
174 };
175
176 let context = direct_contexts::make_vulkan(&backend_context, None)
177 .ok_or(SvgRenderError::SkiaContext)?;
178 let resource_provider = CachedResourceProvider::new(FontMgr::default());
179
180 Ok(Self {
181 vulkan,
182 context,
183 resource_provider,
184 readback_buffer: Vec::new(),
185 })
186 }
187
188 pub fn add_resource_search_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
190 self.resource_provider.add_search_dir(dir);
191 self
192 }
193
194 pub fn set_resource_search_dirs<I, P>(&mut self, dirs: I) -> &mut Self
196 where
197 I: IntoIterator<Item = P>,
198 P: Into<PathBuf>,
199 {
200 self.resource_provider.set_search_dirs(dirs);
201 self
202 }
203
204 pub fn render_svg(
208 &mut self,
209 svg: impl AsRef<[u8]>,
210 options: &RenderOptions,
211 ) -> Result<ImageData, SvgRenderError> {
212 let mut surface = self.render_surface(svg, options)?;
213 self.context.flush_and_submit();
214 read_surface_pixels(&mut surface, options, &mut self.readback_buffer)
215 }
216
217 pub fn render_svg_to_png(
221 &mut self,
222 svg: impl AsRef<[u8]>,
223 options: &RenderOptions,
224 ) -> Result<Vec<u8>, SvgRenderError> {
225 let mut surface = self.render_surface(svg, options)?;
226 self.context.flush_and_submit();
227 let image = surface.image_snapshot();
228 let data = png_encoder::encode_image(
229 Some(&mut self.context),
230 &image,
231 &png_encoder::Options::default(),
232 )
233 .ok_or(SvgRenderError::PngEncode)?;
234
235 Ok(data.as_bytes().to_vec())
236 }
237
238 pub fn render_svg_to_jpeg(
240 &mut self,
241 svg: impl AsRef<[u8]>,
242 options: &RenderOptions,
243 jpeg_options: JpegOptions,
244 ) -> Result<Vec<u8>, SvgRenderError> {
245 let mut surface = self.render_surface(svg, options)?;
246 self.context.flush_and_submit();
247 let image = surface.image_snapshot();
248 let data =
249 jpeg_encoder::encode_image(Some(&mut self.context), &image, &jpeg_options.into())
250 .ok_or(SvgRenderError::JpegEncode)?;
251
252 Ok(data.as_bytes().to_vec())
253 }
254
255 pub fn render_svg_to_webp(
257 &mut self,
258 svg: impl AsRef<[u8]>,
259 options: &RenderOptions,
260 webp_options: WebpOptions,
261 ) -> Result<Vec<u8>, SvgRenderError> {
262 let mut surface = self.render_surface(svg, options)?;
263 self.context.flush_and_submit();
264 let image = surface.image_snapshot();
265 let data =
266 webp_encoder::encode_image(Some(&mut self.context), &image, &webp_options.into())
267 .ok_or(SvgRenderError::WebpEncode)?;
268
269 Ok(data.as_bytes().to_vec())
270 }
271
272 fn render_surface(
274 &mut self,
275 svg: impl AsRef<[u8]>,
276 options: &RenderOptions,
277 ) -> Result<skia_safe::Surface, SvgRenderError> {
278 let (width, height) = options.size.as_i32_pair();
279 let info = rgba_image_info(width, height);
280 let mut surface = gpu::surfaces::render_target(
281 &mut self.context,
282 gpu::Budgeted::No,
283 &info,
284 options.sample_count,
285 gpu::SurfaceOrigin::TopLeft,
286 None,
287 false,
288 false,
289 )
290 .ok_or(SvgRenderError::RenderTarget)?;
291 render_dom(surface.canvas(), svg, options, &self.resource_provider)?;
292 Ok(surface)
293 }
294}
295
296#[cfg(feature = "vulkan-backend")]
297impl Drop for VulkanSvgRenderer {
298 fn drop(&mut self) {
299 self.context.abandon();
302 let _ = Arc::strong_count(&self.vulkan);
305 }
306}
307
308pub struct SvgRenderer {
314 renderer: SvgRendererBackend,
315}
316
317enum SvgRendererBackend {
318 Cpu(CpuSvgRenderer),
319 #[cfg(feature = "vulkan-backend")]
320 Vulkan(VulkanSvgRenderer),
321}
322
323impl SvgRenderer {
324 pub fn new() -> Result<Self, SvgRenderError> {
326 #[cfg(feature = "vulkan-backend")]
327 if let Ok(renderer) = VulkanSvgRenderer::new() {
328 return Ok(Self {
329 renderer: SvgRendererBackend::Vulkan(renderer),
330 });
331 }
332
333 Ok(Self {
334 renderer: SvgRendererBackend::Cpu(CpuSvgRenderer::new()?),
335 })
336 }
337
338 pub fn backend(&self) -> RenderBackend {
340 match &self.renderer {
341 SvgRendererBackend::Cpu(_) => RenderBackend::Cpu,
342 #[cfg(feature = "vulkan-backend")]
343 SvgRendererBackend::Vulkan(_) => RenderBackend::Vulkan,
344 }
345 }
346
347 pub fn add_resource_search_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
349 match &mut self.renderer {
350 SvgRendererBackend::Cpu(renderer) => {
351 renderer.add_resource_search_dir(dir);
352 }
353 #[cfg(feature = "vulkan-backend")]
354 SvgRendererBackend::Vulkan(renderer) => {
355 renderer.add_resource_search_dir(dir);
356 }
357 }
358 self
359 }
360
361 pub fn set_resource_search_dirs<I, P>(&mut self, dirs: I) -> &mut Self
363 where
364 I: IntoIterator<Item = P>,
365 P: Into<PathBuf>,
366 {
367 match &mut self.renderer {
368 SvgRendererBackend::Cpu(renderer) => {
369 renderer.set_resource_search_dirs(dirs);
370 }
371 #[cfg(feature = "vulkan-backend")]
372 SvgRendererBackend::Vulkan(renderer) => {
373 renderer.set_resource_search_dirs(dirs);
374 }
375 }
376 self
377 }
378
379 pub fn render_svg(
381 &mut self,
382 svg: impl AsRef<[u8]>,
383 options: &RenderOptions,
384 ) -> Result<ImageData, SvgRenderError> {
385 match &mut self.renderer {
386 SvgRendererBackend::Cpu(renderer) => renderer.render_svg(svg, options),
387 #[cfg(feature = "vulkan-backend")]
388 SvgRendererBackend::Vulkan(renderer) => renderer.render_svg(svg, options),
389 }
390 }
391
392 pub fn render_svg_to_png(
394 &mut self,
395 svg: impl AsRef<[u8]>,
396 options: &RenderOptions,
397 ) -> Result<Vec<u8>, SvgRenderError> {
398 match &mut self.renderer {
399 SvgRendererBackend::Cpu(renderer) => renderer.render_svg_to_png(svg, options),
400 #[cfg(feature = "vulkan-backend")]
401 SvgRendererBackend::Vulkan(renderer) => renderer.render_svg_to_png(svg, options),
402 }
403 }
404
405 pub fn render_svg_to_jpeg(
407 &mut self,
408 svg: impl AsRef<[u8]>,
409 options: &RenderOptions,
410 jpeg_options: JpegOptions,
411 ) -> Result<Vec<u8>, SvgRenderError> {
412 match &mut self.renderer {
413 SvgRendererBackend::Cpu(renderer) => {
414 renderer.render_svg_to_jpeg(svg, options, jpeg_options)
415 }
416 #[cfg(feature = "vulkan-backend")]
417 SvgRendererBackend::Vulkan(renderer) => {
418 renderer.render_svg_to_jpeg(svg, options, jpeg_options)
419 }
420 }
421 }
422
423 pub fn render_svg_to_webp(
425 &mut self,
426 svg: impl AsRef<[u8]>,
427 options: &RenderOptions,
428 webp_options: WebpOptions,
429 ) -> Result<Vec<u8>, SvgRenderError> {
430 match &mut self.renderer {
431 SvgRendererBackend::Cpu(renderer) => {
432 renderer.render_svg_to_webp(svg, options, webp_options)
433 }
434 #[cfg(feature = "vulkan-backend")]
435 SvgRendererBackend::Vulkan(renderer) => {
436 renderer.render_svg_to_webp(svg, options, webp_options)
437 }
438 }
439 }
440}
441
442fn rgba_image_info(width: i32, height: i32) -> ImageInfo {
444 ImageInfo::new(
445 (width, height),
446 ColorType::RGBA8888,
447 AlphaType::Premul,
448 None,
449 )
450}
451
452fn render_dom(
454 canvas: &skia_safe::Canvas,
455 svg: impl AsRef<[u8]>,
456 options: &RenderOptions,
457 resource_provider: &CachedResourceProvider,
458) -> Result<(), SvgRenderError> {
459 let (width, height) = options.size.as_i32_pair();
460 let resource_provider: NativeResourceProvider = resource_provider.clone().into();
461 let mut dom =
462 Dom::from_bytes(svg.as_ref(), resource_provider).map_err(|_| SvgRenderError::SvgParse)?;
463 dom.set_container_size(Size::new(width as f32, height as f32));
464
465 canvas.clear(options.clear_color);
466 dom.render(canvas);
467 Ok(())
468}
469
470fn read_surface_pixels(
472 surface: &mut skia_safe::Surface,
473 options: &RenderOptions,
474 readback_buffer: &mut Vec<u8>,
475) -> Result<ImageData, SvgRenderError> {
476 let (width, height) = options.size.as_i32_pair();
477 let info = rgba_image_info(width, height);
478 let row_bytes = (width as usize)
479 .checked_mul(4)
480 .ok_or(SvgRenderError::InvalidSize {
481 width: options.size.width,
482 height: options.size.height,
483 })?;
484 let byte_len = row_bytes
485 .checked_mul(height as usize)
486 .ok_or(SvgRenderError::InvalidSize {
487 width: options.size.width,
488 height: options.size.height,
489 })?;
490 if byte_len > MAX_RENDER_BYTES {
491 return Err(SvgRenderError::InvalidSize {
492 width: options.size.width,
493 height: options.size.height,
494 });
495 }
496 readback_buffer.resize(byte_len, 0);
497
498 if !surface.read_pixels(&info, readback_buffer, row_bytes, IPoint::new(0, 0)) {
499 return Err(SvgRenderError::ReadPixels);
500 }
501
502 Ok(ImageData {
503 width: options.size.width,
504 height: options.size.height,
505 row_bytes,
506 rgba: readback_buffer.clone(),
507 })
508}
509
510pub fn render_svg(
512 svg: impl AsRef<[u8]>,
513 options: &RenderOptions,
514) -> Result<ImageData, SvgRenderError> {
515 SvgRenderer::new()?.render_svg(svg, options)
516}
517
518pub fn render_svg_to_png(
520 svg: impl AsRef<[u8]>,
521 options: &RenderOptions,
522) -> Result<Vec<u8>, SvgRenderError> {
523 SvgRenderer::new()?.render_svg_to_png(svg, options)
524}
525
526pub fn render_svg_to_jpeg(
528 svg: impl AsRef<[u8]>,
529 options: &RenderOptions,
530 jpeg_options: JpegOptions,
531) -> Result<Vec<u8>, SvgRenderError> {
532 SvgRenderer::new()?.render_svg_to_jpeg(svg, options, jpeg_options)
533}
534
535pub fn render_svg_to_webp(
537 svg: impl AsRef<[u8]>,
538 options: &RenderOptions,
539 webp_options: WebpOptions,
540) -> Result<Vec<u8>, SvgRenderError> {
541 SvgRenderer::new()?.render_svg_to_webp(svg, options, webp_options)
542}