use std::io::BufWriter;
use vello_common::kurbo::{Affine, Stroke};
use vello_common::peniko::ImageAlphaType;
use vello_common::pico_svg::{Item, PicoSvg};
use vello_common::pixmap::{PixelMetadata, Pixmap};
use vello_gpu::{DimensionConstraints, Scene};
fn main() {
pollster::block_on(run());
}
async fn run() {
let mut args = std::env::args().skip(1);
let svg_filename: String = args.next().expect("svg filename is first arg");
let output_filename: String = args.next().expect("output filename is second arg");
let svg = std::fs::read_to_string(svg_filename).expect("error reading file");
let render_scale = 5.0;
let parsed = PicoSvg::load(&svg, 1.0).expect("error parsing SVG");
let constraints = DimensionConstraints::default();
let svg_width = parsed.size.width * render_scale;
let svg_height = parsed.size.height * render_scale;
let (width, height) = constraints.calculate_dimensions(svg_width, svg_height);
let width = DimensionConstraints::convert_dimension(width);
let height = DimensionConstraints::convert_dimension(height);
let mut scene = Scene::new(width, height);
render_svg(&mut scene, &parsed.items, Affine::scale(render_scale));
let instance = wgpu::Instance::default();
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface: None,
..Default::default()
}))
.expect("Failed to find an appropriate adapter");
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("Device"),
required_features: wgpu::Features::empty(),
..Default::default()
}))
.expect("Failed to create device");
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Render Target"),
size: wgpu::Extent3d {
width: width.into(),
height: height.into(),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let (mut renderer, mut resources) = vello_gpu::Renderer::new(
&device,
&vello_gpu::RenderTargetConfig {
format: texture.format(),
width,
height,
},
);
let render_size = vello_gpu::RenderSize { width, height };
let depth_texture_view = vello_gpu::Renderer::create_depth_texture_view(&device, &render_size);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Vello Render To Buffer"),
});
renderer
.render(
&scene,
&mut resources,
&device,
&queue,
&mut encoder,
&render_size,
&texture_view,
Some(&depth_texture_view),
&vello_gpu::TextureBindings::new(),
vello_gpu::TargetInit::Clear(vello_gpu::ClearSettings::default()),
)
.unwrap();
let bytes_per_row = (u32::from(width) * 4).next_multiple_of(256);
let texture_copy_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Output Buffer"),
size: u64::from(bytes_per_row) * u64::from(height),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &texture_copy_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: None,
},
},
wgpu::Extent3d {
width: width.into(),
height: height.into(),
depth_or_array_layers: 1,
},
);
queue.submit([encoder.finish()]);
texture_copy_buffer
.slice(..)
.map_async(wgpu::MapMode::Read, move |result| {
if result.is_err() {
panic!("Failed to map texture for reading");
}
});
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
let mut img_data = Vec::with_capacity(usize::from(width) * usize::from(height) * 4);
for row in texture_copy_buffer
.slice(..)
.get_mapped_range()
.unwrap()
.chunks_exact(bytes_per_row as usize)
{
img_data.extend_from_slice(&row[0..usize::from(width) * 4]);
}
texture_copy_buffer.unmap();
let pixmap = Pixmap::from_parts(img_data, width, height, PixelMetadata::default());
let file = std::fs::File::create(output_filename).unwrap();
let w = BufWriter::new(file);
let mut png_encoder = png::Encoder::new(w, width.into(), height.into());
png_encoder.set_color(png::ColorType::Rgba);
let mut writer = png_encoder.write_header().unwrap();
writer
.write_image_data(&pixmap.take_rgba8(ImageAlphaType::Alpha))
.unwrap();
}
fn render_svg(ctx: &mut Scene, items: &[Item], transform: Affine) {
ctx.set_transform(transform);
for item in items {
match item {
Item::Fill(fill_item) => {
ctx.set_paint(fill_item.color);
ctx.fill_path(&fill_item.path);
}
Item::Stroke(stroke_item) => {
let style = Stroke::new(stroke_item.width);
ctx.set_stroke(style);
ctx.set_paint(stroke_item.color);
ctx.stroke_path(&stroke_item.path);
}
Item::Group(group_item) => {
render_svg(ctx, &group_item.children, transform * group_item.affine);
ctx.set_transform(transform);
}
}
}
}