1#![cfg(windows)]
5#![allow(clippy::identity_op)]
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8#![deny(clippy::trivially_copy_pass_by_ref)]
9
10mod conv;
13pub mod d2d;
14pub mod d3d;
15pub mod dwrite;
16mod text;
17
18use std::borrow::Cow;
19use std::ops::Deref;
20
21use associative_cache::{AssociativeCache, Capacity1024, HashFourWay, RoundRobinReplacement};
22
23use winapi::um::d2d1::{
24 D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
25 D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES, D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES,
26};
27use winapi::um::d2d1_1::{D2D1_COMPOSITE_MODE_SOURCE_OVER, D2D1_INTERPOLATION_MODE_LINEAR};
28use winapi::um::dcommon::{D2D1_ALPHA_MODE_IGNORE, D2D1_ALPHA_MODE_PREMULTIPLIED};
29
30use piet::kurbo::{Affine, PathEl, Point, Rect, Shape, Size};
31
32use piet::{
33 Color, Error, FixedGradient, Image, ImageFormat, InterpolationMode, IntoBrush, RenderContext,
34 StrokeStyle,
35};
36
37pub use crate::d2d::{D2DDevice, D2DFactory, DeviceContext as D2DDeviceContext};
38use crate::d2d::{Layer, wrap_unit};
39pub use crate::dwrite::DwriteFactory;
40pub use crate::text::{D2DLoadedFonts, D2DText, D2DTextLayout, D2DTextLayoutBuilder};
41
42use crate::conv::{
43 affine_to_matrix3x2f, color_to_colorf, convert_stroke_style, gradient_stop_to_d2d,
44 matrix3x2f_to_affine, rect_to_rectf, rect_to_rectu, to_point2f, to_point2u,
45};
46use crate::d2d::{Bitmap, Brush, DeviceContext, FillRule, Geometry};
47
48pub struct D2DRenderContext<'a> {
49 factory: &'a D2DFactory,
50 inner_text: D2DText,
51 rt: &'a mut D2DDeviceContext,
52
53 ctx_stack: Vec<CtxState>,
55
56 layers: Vec<(Geometry, Layer)>,
57
58 err: Result<(), Error>,
59
60 brush_cache: AssociativeCache<u32, Brush, Capacity1024, HashFourWay, RoundRobinReplacement>,
61}
62
63#[derive(Default)]
64struct CtxState {
65 transform: Affine,
66
67 n_layers_pop: usize,
70}
71
72impl<'b, 'a: 'b> D2DRenderContext<'a> {
73 pub fn new(
77 factory: &'a D2DFactory,
78 text: D2DText,
79 rt: &'b mut DeviceContext,
80 ) -> D2DRenderContext<'b> {
81 D2DRenderContext {
82 factory,
83 inner_text: text,
84 rt,
85 layers: vec![],
86 ctx_stack: vec![CtxState::default()],
87 err: Ok(()),
88 brush_cache: Default::default(),
89 }
90 }
91
92 fn pop_state(&mut self) {
93 let old_state = self.ctx_stack.pop().unwrap();
95 for _ in 0..old_state.n_layers_pop {
96 self.rt.pop_layer();
97 self.layers.pop();
98 }
99 }
100
101 pub fn assert_finished(&mut self) {
106 assert!(
107 self.ctx_stack.last().unwrap().n_layers_pop == 0,
108 "Need to call finish() before using the contents"
109 );
110 }
111}
112
113const BEZ_TOLERANCE: f64 = 1e-3;
118
119fn geometry_from_shape(
120 d2d: &D2DFactory,
121 is_filled: bool,
122 shape: impl Shape,
123 fill_rule: FillRule,
124) -> Result<Geometry, Error> {
125 if let Some(rect) = shape.as_rect() {
127 Ok(d2d.create_rect_geometry(rect)?.into())
128 } else if let Some(round_rect) = shape
129 .as_rounded_rect()
130 .filter(|r| r.radii().as_single_radius().is_some())
131 {
132 Ok(d2d
133 .create_round_rect_geometry(
134 round_rect.rect(),
135 round_rect.radii().as_single_radius().unwrap(),
136 )?
137 .into())
138 } else if let Some(circle) = shape.as_circle() {
139 Ok(d2d.create_circle_geometry(circle)?.into())
140 } else {
141 path_from_shape(d2d, is_filled, shape, fill_rule)
142 }
143}
144
145fn path_from_shape(
146 d2d: &D2DFactory,
147 is_filled: bool,
148 shape: impl Shape,
149 fill_rule: FillRule,
150) -> Result<Geometry, Error> {
151 let mut path = d2d.create_path_geometry()?;
152 let mut sink = path.open()?;
153 sink.set_fill_mode(fill_rule);
154 let mut need_close = false;
155 for el in shape.path_elements(BEZ_TOLERANCE) {
156 match el {
157 PathEl::MoveTo(p) => {
158 if need_close {
159 sink.end_figure(false);
160 }
161 sink.begin_figure(to_point2f(p), is_filled);
162 need_close = true;
163 }
164 PathEl::LineTo(p) => {
165 sink.add_line(to_point2f(p));
166 }
167 PathEl::QuadTo(p1, p2) => {
168 sink.add_quadratic_bezier(to_point2f(p1), to_point2f(p2));
169 }
170 PathEl::CurveTo(p1, p2, p3) => {
171 sink.add_bezier(to_point2f(p1), to_point2f(p2), to_point2f(p3));
172 }
173 PathEl::ClosePath => {
174 sink.end_figure(true);
175 need_close = false;
176 }
177 }
178 }
179 if need_close {
180 sink.end_figure(false);
181 }
182 sink.close()?;
183 Ok(path.into())
184}
185
186impl<'a> RenderContext for D2DRenderContext<'a> {
187 type Brush = Brush;
188
189 type Text = D2DText;
190
191 type TextLayout = D2DTextLayout;
192
193 type Image = Bitmap;
194
195 fn status(&mut self) -> Result<(), Error> {
196 std::mem::replace(&mut self.err, Ok(()))
197 }
198
199 fn clear(&mut self, region: impl Into<Option<Rect>>, color: Color) {
200 for _ in 0..self.layers.len() {
202 self.rt.pop_layer();
203 }
204
205 let old_transform = self.rt.get_transform();
207 self.rt.set_transform_identity();
208
209 if let Some(rect) = region.into() {
210 self.rt.push_axis_aligned_clip(rect);
212 self.rt.clear(color_to_colorf(color));
213 self.rt.pop_axis_aligned_clip();
214 } else {
215 self.rt.clear(color_to_colorf(color));
217 }
218
219 self.rt.set_transform(&old_transform);
221
222 for (mask, layer) in self.layers.iter() {
224 self.rt.push_layer_mask(mask, layer);
225 }
226 }
227
228 fn solid_brush(&mut self, color: Color) -> Brush {
229 let device_context = &mut self.rt;
230 let key = color.as_rgba_u32();
231 self.brush_cache
232 .entry(&key)
233 .or_insert_with(
234 || key,
235 || {
236 device_context
237 .create_solid_color(color_to_colorf(color))
238 .expect("error creating solid brush")
239 },
240 )
241 .clone()
242 }
243
244 fn gradient(&mut self, gradient: impl Into<FixedGradient>) -> Result<Brush, Error> {
245 match gradient.into() {
246 FixedGradient::Linear(linear) => {
247 let props = D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES {
248 startPoint: to_point2f(linear.start),
249 endPoint: to_point2f(linear.end),
250 };
251 let stops: Vec<_> = linear.stops.iter().map(gradient_stop_to_d2d).collect();
252 let stops = self.rt.create_gradient_stops(&stops)?;
253 let result = self.rt.create_linear_gradient(&props, &stops)?;
254 Ok(result)
255 }
256 FixedGradient::Radial(radial) => {
257 let props = D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES {
258 center: to_point2f(radial.center),
259 gradientOriginOffset: to_point2f(radial.origin_offset),
260 radiusX: radial.radius as f32,
261 radiusY: radial.radius as f32,
262 };
263 let stops: Vec<_> = radial.stops.iter().map(gradient_stop_to_d2d).collect();
264 let stops = self.rt.create_gradient_stops(&stops)?;
265 let result = self.rt.create_radial_gradient(&props, &stops)?;
266 Ok(result)
267 }
268 }
269 }
270
271 fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
272 self.fill_impl(shape, brush, FillRule::NonZero)
273 }
274
275 fn fill_even_odd(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
276 self.fill_impl(shape, brush, FillRule::EvenOdd)
277 }
278
279 fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
280 self.stroke_impl(shape, brush, width, None)
281 }
282
283 fn stroke_styled(
284 &mut self,
285 shape: impl Shape,
286 brush: &impl IntoBrush<Self>,
287 width: f64,
288 style: &StrokeStyle,
289 ) {
290 let style = convert_stroke_style(self.factory, style, width)
291 .expect("stroke style conversion failed");
292 self.stroke_impl(shape, brush, width, Some(&style));
293 }
294
295 fn clip(&mut self, shape: impl Shape) {
296 let layer = match self.rt.create_layer(None) {
298 Ok(layer) => layer,
299 Err(e) => {
300 self.err = Err(e.into());
301 return;
302 }
303 };
304 let geom = match geometry_from_shape(self.factory, true, shape, FillRule::NonZero) {
305 Ok(geom) => geom,
306 Err(e) => {
307 self.err = Err(e);
308 return;
309 }
310 };
311 self.rt.push_layer_mask(&geom, &layer);
312 self.layers.push((geom, layer));
313 self.ctx_stack.last_mut().unwrap().n_layers_pop += 1;
314 }
315
316 fn text(&mut self) -> &mut Self::Text {
317 &mut self.inner_text
318 }
319
320 fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<Point>) {
321 layout.draw(pos.into(), self);
323 }
324
325 fn save(&mut self) -> Result<(), Error> {
326 let new_state = CtxState {
327 transform: self.current_transform(),
328 n_layers_pop: 0,
329 };
330 self.ctx_stack.push(new_state);
331 Ok(())
332 }
333
334 fn restore(&mut self) -> Result<(), Error> {
335 if self.ctx_stack.len() <= 1 {
336 return Err(Error::StackUnbalance);
337 }
338 self.pop_state();
339 self.rt
341 .set_transform(&affine_to_matrix3x2f(self.current_transform()));
342
343 Ok(())
344 }
345
346 fn finish(&mut self) -> Result<(), Error> {
350 if self.ctx_stack.len() != 1 {
351 return Err(Error::StackUnbalance);
352 }
353 self.pop_state();
354 std::mem::replace(&mut self.err, Ok(()))
355 }
356
357 fn transform(&mut self, transform: Affine) {
358 self.ctx_stack.last_mut().unwrap().transform *= transform;
359 self.rt
360 .set_transform(&affine_to_matrix3x2f(self.current_transform()));
361 }
362
363 fn current_transform(&self) -> Affine {
364 self.ctx_stack.last().unwrap().transform
366 }
367
368 fn make_image_with_stride(
369 &mut self,
370 width: usize,
371 height: usize,
372 stride: usize,
373 buf: &[u8],
374 format: ImageFormat,
375 ) -> Result<Self::Image, Error> {
376 if width == 0 || height == 0 {
380 return Ok(self.rt.create_empty_bitmap()?);
381 }
382
383 if buf.len()
384 < piet::util::expected_image_buffer_size(
385 format.bytes_per_pixel() * width,
386 height,
387 stride,
388 )
389 {
390 return Err(Error::InvalidInput);
391 }
392
393 let alpha_mode = match format {
395 ImageFormat::Rgb | ImageFormat::Grayscale => D2D1_ALPHA_MODE_IGNORE,
396 ImageFormat::RgbaPremul | ImageFormat::RgbaSeparate => D2D1_ALPHA_MODE_PREMULTIPLIED,
397 _ => return Err(Error::NotSupported),
398 };
399 let buf = match format {
400 ImageFormat::Rgb => {
401 let mut new_buf = vec![255; width * height * 4];
402 for y in 0..height {
403 for x in 0..width {
404 let src_offset = y * stride + x * 3;
405 let dst_offset = (y * width + x) * 4;
406 new_buf[dst_offset + 0] = buf[src_offset + 0];
407 new_buf[dst_offset + 1] = buf[src_offset + 1];
408 new_buf[dst_offset + 2] = buf[src_offset + 2];
409 }
410 }
411 Cow::from(new_buf)
412 }
413 ImageFormat::RgbaSeparate => {
414 let mut new_buf = vec![255; width * height * 4];
415 fn premul(x: u8, a: u8) -> u8 {
417 let y = (x as u16) * (a as u16);
418 ((y + (y >> 8) + 0x80) >> 8) as u8
419 }
420 for y in 0..height {
421 for x in 0..width {
422 let src_offset = y * stride + x * 4;
423 let dst_offset = (y * width + x) * 4;
424 let a = buf[src_offset + 3];
425 new_buf[dst_offset + 0] = premul(buf[src_offset + 0], a);
426 new_buf[dst_offset + 1] = premul(buf[src_offset + 1], a);
427 new_buf[dst_offset + 2] = premul(buf[src_offset + 2], a);
428 new_buf[dst_offset + 3] = a;
429 }
430 }
431 Cow::from(new_buf)
432 }
433 ImageFormat::RgbaPremul => {
434 if stride == width * format.bytes_per_pixel() {
435 Cow::from(buf)
436 } else {
437 Cow::from(piet::util::image_buffer_to_tightly_packed(
438 buf, width, height, stride, format,
439 )?)
440 }
441 }
442 ImageFormat::Grayscale => {
443 let mut new_buf = vec![255; width * height * 4];
447 for y in 0..height {
448 for x in 0..width {
449 let src_offset = y * stride + x;
450 let dst_offset = (y * width + x) * 4;
451 new_buf[dst_offset + 0] = buf[src_offset];
452 new_buf[dst_offset + 1] = buf[src_offset];
453 new_buf[dst_offset + 2] = buf[src_offset];
454 }
455 }
456 Cow::from(new_buf)
457 }
458 _ => return Err(Error::NotSupported),
460 };
461 let bitmap = self.rt.create_bitmap(width, height, &buf, alpha_mode)?;
462 Ok(bitmap)
463 }
464
465 #[inline]
466 fn draw_image(
467 &mut self,
468 image: &Self::Image,
469 dst_rect: impl Into<Rect>,
470 interp: InterpolationMode,
471 ) {
472 draw_image(self.rt, image, None, dst_rect.into(), interp);
473 }
474
475 #[inline]
476 fn draw_image_area(
477 &mut self,
478 image: &Self::Image,
479 src_rect: impl Into<Rect>,
480 dst_rect: impl Into<Rect>,
481 interp: InterpolationMode,
482 ) {
483 draw_image(
484 self.rt,
485 image,
486 Some(src_rect.into()),
487 dst_rect.into(),
488 interp,
489 );
490 }
491
492 fn capture_image_area(&mut self, rect: impl Into<Rect>) -> Result<Self::Image, Error> {
493 let r = rect.into();
494
495 let (dpi_scale, _) = self.rt.get_dpi_scale();
496 let dpi_scale = dpi_scale as f64;
497
498 let transform_matrix = self.rt.get_transform();
499 let affine_transform = matrix3x2f_to_affine(transform_matrix);
500
501 let device_size = Point {
502 x: r.width() * dpi_scale,
503 y: r.height() * dpi_scale,
504 };
505 let device_size = affine_transform * device_size;
507 let device_size = device_size.to_vec2().to_size();
508
509 let device_origin = Point {
510 x: r.x0 * dpi_scale,
511 y: r.y0 * dpi_scale,
512 };
513 let device_origin = affine_transform * device_origin;
515
516 let mut target_bitmap = self.rt.create_blank_bitmap(
517 device_size.width as usize,
518 device_size.height as usize,
519 dpi_scale as f32,
520 )?;
521
522 let src_rect = Rect::from_origin_size(device_origin, device_size);
523
524 let d2d_dest_point = to_point2u((0.0f32, 0.0f32));
525 let d2d_src_rect = rect_to_rectu(src_rect);
526
527 for _ in 0..self.layers.len() {
529 self.rt.pop_layer();
530 }
531
532 target_bitmap.copy_from_render_target(d2d_dest_point, self.rt, d2d_src_rect);
533
534 for (mask, layer) in self.layers.iter() {
536 self.rt.push_layer_mask(mask, layer);
537 }
538
539 Ok(target_bitmap)
540 }
541
542 fn blurred_rect(&mut self, rect: Rect, blur_radius: f64, brush: &impl IntoBrush<Self>) {
543 let brush = brush.make_brush(self, || rect);
544 if let Err(e) = self.blurred_rect_raw(rect, blur_radius, brush) {
545 eprintln!("error in drawing blurred rect: {e:?}");
546 }
547 }
548}
549
550impl<'a> D2DRenderContext<'a> {
551 fn fill_impl(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, fill_rule: FillRule) {
552 let brush = brush.make_brush(self, || shape.bounding_box());
553
554 if let Some(rect) = shape.as_rect() {
556 self.rt.fill_rect(rect, &brush)
557 } else if let Some(round_rect) = shape
558 .as_rounded_rect()
559 .filter(|r| r.radii().as_single_radius().is_some())
560 {
561 self.rt.fill_rounded_rect(
562 round_rect.rect(),
563 round_rect.radii().as_single_radius().unwrap(),
564 &brush,
565 )
566 } else if let Some(circle) = shape.as_circle() {
567 self.rt.fill_circle(circle, &brush)
568 } else {
569 match path_from_shape(self.factory, true, shape, fill_rule) {
570 Ok(geom) => self.rt.fill_geometry(&geom, &brush, None),
571 Err(e) => self.err = Err(e),
572 }
573 }
574 }
575
576 fn stroke_impl(
577 &mut self,
578 shape: impl Shape,
579 brush: &impl IntoBrush<Self>,
580 width: f64,
581 style: Option<&crate::d2d::StrokeStyle>,
582 ) {
583 let brush = brush.make_brush(self, || shape.bounding_box());
584 let width = width as f32;
585
586 if let Some(line) = shape.as_line() {
587 self.rt.draw_line(line, &brush, width, style);
588 return;
589 } else if let Some(rect) = shape.as_rect() {
590 self.rt.draw_rect(rect, &brush, width, style);
591 return;
592 } else if let Some(round_rect) = shape.as_rounded_rect() {
593 if let Some(radius) = round_rect.radii().as_single_radius() {
594 self.rt
595 .draw_rounded_rect(round_rect.rect(), radius, &brush, width, style);
596 return;
597 }
598 } else if let Some(circle) = shape.as_circle() {
599 self.rt.draw_circle(circle, &brush, width, style);
600 return;
601 }
602
603 let geom = match path_from_shape(self.factory, false, shape, FillRule::EvenOdd) {
604 Ok(geom) => geom,
605 Err(e) => {
606 self.err = Err(e);
607 return;
608 }
609 };
610 self.rt.draw_geometry(&geom, &brush, width, style);
611 }
612
613 fn blurred_rect_raw(
616 &mut self,
617 rect: Rect,
618 blur_radius: f64,
619 brush: Cow<Brush>,
620 ) -> Result<(), Error> {
621 let rect_exp = rect.expand();
622 let widthf = rect_exp.width() as f32;
623 let heightf = rect_exp.height() as f32;
624 let brt = self.rt.create_compatible_render_target(widthf, heightf)?;
627 let clear_color = winapi::um::d2d1::D2D1_COLOR_F {
630 r: 0.0,
631 g: 0.0,
632 b: 0.0,
633 a: 0.0,
634 };
635 let draw_rect = rect_to_rectf(rect - rect_exp.origin().to_vec2());
636 unsafe {
637 brt.BeginDraw();
638 brt.Clear(&clear_color);
639 brt.FillRectangle(&draw_rect, brush.as_raw());
640 let mut tag1 = 0;
641 let mut tag2 = 0;
642 let hr = brt.EndDraw(&mut tag1, &mut tag2);
643 wrap_unit(hr)?;
644 }
645 let effect = self.rt.create_blur_effect(blur_radius)?;
648 let bitmap = brt.get_bitmap()?;
649 effect.set_input(0, bitmap.deref());
650 let offset = to_point2f(rect_exp.origin());
651 self.rt.draw_image_effect(
652 &effect,
653 Some(offset),
654 None,
655 D2D1_INTERPOLATION_MODE_LINEAR,
656 D2D1_COMPOSITE_MODE_SOURCE_OVER,
657 );
658 Ok(())
659 }
660}
661
662impl<'a> Drop for D2DRenderContext<'a> {
663 fn drop(&mut self) {
664 assert!(
665 self.ctx_stack.is_empty(),
666 "Render context dropped without finish() call"
667 );
668 }
669}
670
671fn draw_image<'a>(
672 rt: &'a mut D2DDeviceContext,
673 image: &<D2DRenderContext<'a> as RenderContext>::Image,
674 src_rect: Option<Rect>,
675 dst_rect: Rect,
676 interp: InterpolationMode,
677) {
678 if dst_rect.is_zero_area() || image.empty_image {
679 return;
681 }
682 let interp = match interp {
683 InterpolationMode::NearestNeighbor => D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
684 InterpolationMode::Bilinear => D2D1_BITMAP_INTERPOLATION_MODE_LINEAR,
685 };
686 let src_rect = src_rect.map(rect_to_rectf);
687 rt.draw_bitmap(
688 image,
689 &rect_to_rectf(dst_rect),
690 1.0,
691 interp,
692 src_rect.as_ref(),
693 );
694}
695
696impl<'a> IntoBrush<D2DRenderContext<'a>> for Brush {
697 fn make_brush<'b>(
698 &'b self,
699 _piet: &mut D2DRenderContext,
700 _bbox: impl FnOnce() -> Rect,
701 ) -> std::borrow::Cow<'b, Brush> {
702 Cow::Borrowed(self)
703 }
704}
705
706impl Image for Bitmap {
707 fn size(&self) -> Size {
708 if self.empty_image {
709 Size::ZERO
710 } else {
711 let inner = self.get_size();
712 Size::new(inner.width.into(), inner.height.into())
713 }
714 }
715}