piet_direct2d/
d2d.rs

1// Copyright 2020 the Piet Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Convenience wrappers for Direct2D objects.
5//!
6//! These also function as safety boundaries (though determining the
7//! exact safety guarantees is work in progress).
8
9// TODO: get rid of this when we actually do use everything
10#![allow(unused)]
11
12use std::ffi::c_void;
13use std::fmt::{Debug, Display, Formatter};
14use std::marker::PhantomData;
15use std::ops::Deref;
16use std::ptr::{null, null_mut};
17
18use piet::kurbo::{Circle, Line, Rect, RoundedRect};
19
20use wio::com::ComPtr;
21
22use winapi::Interface;
23use winapi::shared::dxgi::{IDXGIDevice, IDXGISurface};
24use winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM;
25use winapi::shared::minwindef::TRUE;
26use winapi::shared::winerror::{HRESULT, SUCCEEDED};
27use winapi::um::d2d1::{
28    D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1_BEZIER_SEGMENT, D2D1_BITMAP_INTERPOLATION_MODE,
29    D2D1_BRUSH_PROPERTIES, D2D1_COLOR_F, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
30    D2D1_DEBUG_LEVEL_NONE, D2D1_DEBUG_LEVEL_WARNING, D2D1_DRAW_TEXT_OPTIONS,
31    D2D1_EXTEND_MODE_CLAMP, D2D1_FACTORY_OPTIONS, D2D1_FACTORY_TYPE_MULTI_THREADED,
32    D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_BEGIN_HOLLOW, D2D1_FIGURE_END_CLOSED,
33    D2D1_FIGURE_END_OPEN, D2D1_FILL_MODE_ALTERNATE, D2D1_FILL_MODE_WINDING, D2D1_GAMMA_2_2,
34    D2D1_GRADIENT_STOP, D2D1_LAYER_OPTIONS_NONE, D2D1_LAYER_PARAMETERS,
35    D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES, D2D1_MATRIX_3X2_F, D2D1_POINT_2F, D2D1_POINT_2U,
36    D2D1_QUADRATIC_BEZIER_SEGMENT, D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES, D2D1_RECT_F, D2D1_RECT_U,
37    D2D1_SIZE_F, D2D1_SIZE_U, D2D1_STROKE_STYLE_PROPERTIES, D2D1CreateFactory, ID2D1Bitmap,
38    ID2D1BitmapRenderTarget, ID2D1Brush, ID2D1EllipseGeometry, ID2D1Geometry, ID2D1GeometrySink,
39    ID2D1GradientStopCollection, ID2D1Image, ID2D1Layer, ID2D1PathGeometry, ID2D1RectangleGeometry,
40    ID2D1RenderTarget, ID2D1RoundedRectangleGeometry, ID2D1SolidColorBrush, ID2D1StrokeStyle,
41};
42use winapi::um::d2d1_1::{
43    D2D1_BITMAP_OPTIONS_NONE, D2D1_BITMAP_OPTIONS_TARGET, D2D1_BITMAP_PROPERTIES1,
44    D2D1_COMPOSITE_MODE, D2D1_DEVICE_CONTEXT_OPTIONS_NONE, D2D1_INTERPOLATION_MODE,
45    D2D1_PROPERTY_TYPE_FLOAT, ID2D1Bitmap1, ID2D1Device, ID2D1DeviceContext, ID2D1Effect,
46    ID2D1Factory1,
47};
48use winapi::um::d2d1_1::{D2D1_PRIMITIVE_BLEND_COPY, D2D1_PRIMITIVE_BLEND_SOURCE_OVER};
49use winapi::um::d2d1effects::{CLSID_D2D1GaussianBlur, D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION};
50use winapi::um::dcommon::{D2D1_ALPHA_MODE, D2D1_ALPHA_MODE_PREMULTIPLIED, D2D1_PIXEL_FORMAT};
51
52use crate::conv::{circle_to_d2d, rect_to_rectf, rounded_rect_to_d2d, to_point2f};
53use crate::dwrite::TextLayout;
54
55pub enum FillRule {
56    EvenOdd,
57    NonZero,
58}
59
60pub enum Error {
61    WinapiError(HRESULT),
62}
63
64/// A Direct2D factory object.
65///
66/// This struct is public only to use for system integration in piet_common and druid-shell. It is not intended
67/// that end-users directly use this struct.
68pub struct D2DFactory(ComPtr<ID2D1Factory1>);
69
70/// A Direct2D device.
71pub struct D2DDevice(ComPtr<ID2D1Device>);
72
73// Microsoft's API docs suggest that it's safe to access D2D factories, and anything coming out of
74// them, from multiple threads. (In fact, if there's no underlying D3D resources, they're even
75// `Sync`.) https://docs.microsoft.com/en-us/windows/win32/direct2d/multi-threaded-direct2d-apps
76unsafe impl Send for D2DFactory {}
77unsafe impl Send for D2DDevice {}
78
79/// The main context that takes drawing operations.
80///
81/// This type is a thin wrapper for
82/// [ID2D1DeviceContext](https://docs.microsoft.com/en-us/windows/win32/api/d2d1_1/nn-d2d1_1-id2d1devicecontext).
83///
84/// This struct is public only to use for system integration in piet_common and druid-shell. It is not intended
85/// that end-users directly use this struct.
86#[derive(Clone)]
87pub struct DeviceContext(ComPtr<ID2D1DeviceContext>);
88
89pub struct PathGeometry(ComPtr<ID2D1PathGeometry>);
90
91pub struct RectangleGeometry(ComPtr<ID2D1RectangleGeometry>);
92
93pub struct RoundedRectangleGeometry(ComPtr<ID2D1RoundedRectangleGeometry>);
94
95pub struct EllipseGeometry(ComPtr<ID2D1EllipseGeometry>);
96
97pub struct Geometry(ComPtr<ID2D1Geometry>);
98
99pub struct GeometrySink<'a> {
100    ptr: ComPtr<ID2D1GeometrySink>,
101    // The PhantomData keeps us from doing stuff like having
102    // two GeometrySink objects open on the same PathGeometry.
103    //
104    // It's conservative, but helps avoid logic errors.
105    marker: PhantomData<&'a mut PathGeometry>,
106}
107
108pub struct GradientStopCollection(ComPtr<ID2D1GradientStopCollection>);
109
110// TODO: consider not building this at all, but just Brush.
111pub struct SolidColorBrush(ComPtr<ID2D1SolidColorBrush>);
112
113pub struct StrokeStyle(ComPtr<ID2D1StrokeStyle>);
114
115pub struct Layer(ComPtr<ID2D1Layer>);
116
117#[derive(Clone)]
118pub struct Brush(ComPtr<ID2D1Brush>);
119
120#[derive(Clone)]
121pub struct Bitmap {
122    inner: ComPtr<ID2D1Bitmap1>,
123    pub(crate) empty_image: bool,
124}
125
126#[derive(Debug)]
127pub struct Image {
128    inner: ComPtr<ID2D1Image>,
129}
130
131pub struct Effect(ComPtr<ID2D1Effect>);
132
133// Note: there may be an opportunity here to combine with the version in
134// piet-common direct2d_back, but the use cases are somewhat different.
135pub struct BitmapRenderTarget(ComPtr<ID2D1BitmapRenderTarget>);
136
137/// Restarts drawing when dropped.
138///
139/// Specifically, this struct's `Drop` implementation calls [`DeviceContext::begin_draw`].
140///
141/// This is useful when you need to restart drawing but have multiple return paths.
142pub struct DrawRestarter<'a> {
143    context: &'a mut DeviceContext,
144}
145
146impl<'a> Drop for DrawRestarter<'a> {
147    fn drop(&mut self) {
148        self.context.begin_draw();
149    }
150}
151
152impl From<HRESULT> for Error {
153    fn from(hr: HRESULT) -> Error {
154        Error::WinapiError(hr)
155    }
156}
157
158impl Debug for Error {
159    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
160        match self {
161            Error::WinapiError(hr) => write!(f, "hresult {hr:x}"),
162        }
163    }
164}
165
166impl Display for Error {
167    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
168        match self {
169            Error::WinapiError(hr) => write!(f, "hresult {hr:x}"),
170        }
171    }
172}
173
174impl std::error::Error for Error {
175    fn description(&self) -> &str {
176        "winapi error"
177    }
178}
179
180impl From<Error> for piet::Error {
181    fn from(e: Error) -> piet::Error {
182        piet::Error::BackendError(Box::new(e))
183    }
184}
185
186impl From<PathGeometry> for Geometry {
187    fn from(pg: PathGeometry) -> Self {
188        Geometry(pg.0.up())
189    }
190}
191
192impl From<RectangleGeometry> for Geometry {
193    fn from(pg: RectangleGeometry) -> Self {
194        Geometry(pg.0.up())
195    }
196}
197
198impl From<RoundedRectangleGeometry> for Geometry {
199    fn from(pg: RoundedRectangleGeometry) -> Self {
200        Geometry(pg.0.up())
201    }
202}
203
204impl From<EllipseGeometry> for Geometry {
205    fn from(pg: EllipseGeometry) -> Self {
206        Geometry(pg.0.up())
207    }
208}
209
210unsafe fn wrap<T, U, F>(hr: HRESULT, ptr: *mut T, f: F) -> Result<U, Error>
211where
212    F: Fn(ComPtr<T>) -> U,
213    T: Interface,
214{
215    if SUCCEEDED(hr) {
216        Ok(f(unsafe { ComPtr::from_raw(ptr) }))
217    } else {
218        Err(hr.into())
219    }
220}
221
222pub(crate) fn wrap_unit(hr: HRESULT) -> Result<(), Error> {
223    if SUCCEEDED(hr) {
224        Ok(())
225    } else {
226        Err(hr.into())
227    }
228}
229
230fn optional<T>(val: &Option<T>) -> *const T {
231    val.as_ref().map(|x| x as *const T).unwrap_or(null())
232}
233
234fn stroke_style_to_d2d(style: Option<&StrokeStyle>) -> *mut ID2D1StrokeStyle {
235    style.map(|ss| ss.0.as_raw()).unwrap_or(null_mut())
236}
237
238impl D2DFactory {
239    /// Create a new Direct2D factory.
240    ///
241    /// This requires Windows 7 platform update, and can also fail if
242    /// resources are unavailable.
243    pub fn new() -> Result<D2DFactory, Error> {
244        unsafe {
245            let mut ptr: *mut ID2D1Factory1 = null_mut();
246            let hr = D2D1CreateFactory(
247                D2D1_FACTORY_TYPE_MULTI_THREADED,
248                &ID2D1Factory1::uuidof(),
249                &D2D1_FACTORY_OPTIONS {
250                    // The debug layer should never be active in the release version of an application.
251                    // https://docs.microsoft.com/en-us/windows/win32/Direct2D/direct2ddebuglayer-overview
252                    debugLevel: match cfg!(debug_assertions) {
253                        true => D2D1_DEBUG_LEVEL_WARNING,
254                        false => D2D1_DEBUG_LEVEL_NONE,
255                    },
256                },
257                &mut ptr as *mut _ as *mut _,
258            );
259            wrap(hr, ptr, D2DFactory)
260        }
261    }
262
263    // Would it be safe to take &ComPtr<IDXGIDevice> here?
264    /// Create D2D Device
265    /// # Safety
266    /// TODO
267    pub unsafe fn create_device(&self, dxgi_device: *mut IDXGIDevice) -> Result<D2DDevice, Error> {
268        let mut ptr = null_mut();
269        unsafe {
270            let hr = self.0.CreateDevice(dxgi_device, &mut ptr);
271            wrap(hr, ptr, D2DDevice)
272        }
273    }
274
275    /// Get the raw pointer
276    pub fn get_raw(&self) -> *mut ID2D1Factory1 {
277        self.0.as_raw()
278    }
279
280    pub fn create_path_geometry(&self) -> Result<PathGeometry, Error> {
281        unsafe {
282            let mut ptr = null_mut();
283            let hr = self.0.deref().deref().CreatePathGeometry(&mut ptr);
284            wrap(hr, ptr, PathGeometry)
285        }
286    }
287
288    pub fn create_rect_geometry(&self, rect: Rect) -> Result<RectangleGeometry, Error> {
289        unsafe {
290            let mut ptr = null_mut();
291            let hr = self
292                .0
293                .deref()
294                .deref()
295                .CreateRectangleGeometry(&rect_to_rectf(rect), &mut ptr);
296            wrap(hr, ptr, RectangleGeometry)
297        }
298    }
299
300    pub fn create_round_rect_geometry(
301        &self,
302        rect: Rect,
303        radius: f64,
304    ) -> Result<RoundedRectangleGeometry, Error> {
305        unsafe {
306            let mut ptr = null_mut();
307            let hr = self
308                .0
309                .deref()
310                .deref()
311                .CreateRoundedRectangleGeometry(&rounded_rect_to_d2d(rect, radius), &mut ptr);
312            wrap(hr, ptr, RoundedRectangleGeometry)
313        }
314    }
315
316    pub fn create_circle_geometry(&self, circle: Circle) -> Result<EllipseGeometry, Error> {
317        unsafe {
318            let mut ptr = null_mut();
319            let hr = self
320                .0
321                .deref()
322                .deref()
323                .CreateEllipseGeometry(&circle_to_d2d(circle), &mut ptr);
324            wrap(hr, ptr, EllipseGeometry)
325        }
326    }
327
328    pub fn create_stroke_style(
329        &self,
330        props: &D2D1_STROKE_STYLE_PROPERTIES,
331        dashes: Option<&[f32]>,
332    ) -> Result<StrokeStyle, Error> {
333        unsafe {
334            let mut ptr = null_mut();
335            let dashes_len = dashes.map(|d| d.len()).unwrap_or(0);
336            assert!(dashes_len <= 0xffff_ffff);
337            let hr = self.0.deref().deref().CreateStrokeStyle(
338                props,
339                dashes.map(|d| d.as_ptr()).unwrap_or(null()),
340                dashes_len as u32,
341                &mut ptr,
342            );
343            wrap(hr, ptr, StrokeStyle)
344        }
345    }
346}
347
348impl D2DDevice {
349    /// Create a new device context from the device.
350    ///
351    /// This is a wrapper for
352    /// [ID2D1Device::CreateDeviceContext](https://docs.microsoft.com/en-us/windows/win32/api/d2d1_1/nf-d2d1_1-id2d1device-createdevicecontext).
353    pub fn create_device_context(&mut self) -> Result<DeviceContext, Error> {
354        unsafe {
355            let mut ptr = null_mut();
356            let options = D2D1_DEVICE_CONTEXT_OPTIONS_NONE;
357            let hr = self.0.CreateDeviceContext(options, &mut ptr);
358            wrap(hr, ptr, DeviceContext)
359        }
360    }
361}
362
363const IDENTITY_MATRIX_3X2_F: D2D1_MATRIX_3X2_F = D2D1_MATRIX_3X2_F {
364    matrix: [[1.0, 0.0], [0.0, 1.0], [0.0, 0.0]],
365};
366
367const DEFAULT_BRUSH_PROPERTIES: D2D1_BRUSH_PROPERTIES = D2D1_BRUSH_PROPERTIES {
368    opacity: 1.0,
369    transform: IDENTITY_MATRIX_3X2_F,
370};
371
372impl DeviceContext {
373    /// Create a new device context from an existing COM object.
374    ///
375    /// Marked as unsafe because the device must be in a good state.
376    /// This *might* be overly conservative.
377    ///
378    /// # Safety
379    /// TODO
380    pub unsafe fn new(ptr: ComPtr<ID2D1DeviceContext>) -> DeviceContext {
381        DeviceContext(ptr)
382    }
383
384    /// Get the raw pointer
385    pub fn get_raw(&self) -> *mut ID2D1DeviceContext {
386        self.0.as_raw()
387    }
388
389    /// Get the Com ptr
390    /// TODO rename to `inner`, like for D3D11Device?
391    pub fn get_comptr(&self) -> &ComPtr<ID2D1DeviceContext> {
392        &self.0
393    }
394
395    /// Create a bitmap from a DXGI surface.
396    ///
397    /// Most often, this bitmap will be used to set the target of a
398    /// DeviceContext.
399    ///
400    /// Assumes RGBA8 format and premultiplied alpha.
401    ///
402    /// The `unsafe` might be conservative, but we assume the `dxgi`
403    /// argument is in good shape to be a target.
404    ///
405    /// # Safety
406    /// TODO
407    pub unsafe fn create_bitmap_from_dxgi(
408        &self,
409        dxgi: &ComPtr<IDXGISurface>,
410        dpi_scale: f32,
411    ) -> Result<Bitmap, Error> {
412        let mut ptr = null_mut();
413        let props = D2D1_BITMAP_PROPERTIES1 {
414            pixelFormat: D2D1_PIXEL_FORMAT {
415                format: DXGI_FORMAT_R8G8B8A8_UNORM,
416                alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
417            },
418            dpiX: 96.0 * dpi_scale,
419            dpiY: 96.0 * dpi_scale,
420            bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET,
421            colorContext: null_mut(),
422        };
423        unsafe {
424            let hr = self
425                .0
426                .CreateBitmapFromDxgiSurface(dxgi.as_raw(), &props, &mut ptr);
427            wrap(hr, ptr, |ptr| Bitmap {
428                inner: ptr,
429                // I'm pretty sure an empty dxgi surface will be invalid, so we can be sure the image
430                // is not empty.
431                empty_image: false,
432            })
433        }
434    }
435
436    /// Set the target for the device context.
437    ///
438    /// Useful for rendering into bitmaps.
439    pub fn set_target(&mut self, target: &Bitmap) {
440        assert!(!target.empty_image);
441        unsafe { self.0.SetTarget(target.inner.as_raw() as *mut ID2D1Image) }
442    }
443
444    /// Set the dpi scale.
445    ///
446    /// Mostly useful when rendering into bitmaps.
447    pub fn set_dpi_scale(&mut self, dpi_scale: f32) {
448        unsafe {
449            self.0.SetDpi(96. * dpi_scale, 96. * dpi_scale);
450        }
451    }
452
453    pub fn get_dpi_scale(&self) -> (f32, f32) {
454        let mut dpi_x = 0.0f32;
455        let mut dpi_y = 0.0f32;
456        unsafe {
457            self.0.GetDpi(&mut dpi_x, &mut dpi_y);
458        }
459        // https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-and-high-dpi
460        (dpi_x / 96., dpi_y / 96.)
461    }
462
463    /// Begin drawing.
464    ///
465    /// This must be done before any piet drawing operations.
466    ///
467    /// There may be safety concerns (not clear what happens if the sequence
468    /// is not followed).
469    pub fn begin_draw(&mut self) {
470        unsafe {
471            self.0.BeginDraw();
472        }
473    }
474
475    /// End drawing.
476    pub fn end_draw(&mut self) -> Result<(), Error> {
477        unsafe {
478            let mut tag1 = 0;
479            let mut tag2 = 0;
480            let hr = self.0.EndDraw(&mut tag1, &mut tag2);
481            wrap_unit(hr)
482        }
483    }
484
485    /// End drawing and return a [`DrawRestarter`] which will restart drawing when dropped.
486    pub fn end_draw_temporarily(&mut self) -> Result<DrawRestarter<'_>, Error> {
487        self.end_draw()?;
488        Ok(DrawRestarter { context: self })
489    }
490
491    /// Clip axis aligned clip
492    ///
493    /// Currently this should be for clipping just RenderContext::clear(). If
494    /// this is used for clipping drawing primitives it requires proper stack to
495    /// not interfere with clearing.
496    pub(crate) fn push_axis_aligned_clip(&mut self, rect: Rect) {
497        unsafe {
498            self.0
499                .PushAxisAlignedClip(&rect_to_rectf(rect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
500        }
501    }
502
503    /// Pop axis aligned clip
504    ///
505    /// Currently this should be used for just RenderContext::clear().
506    pub(crate) fn pop_axis_aligned_clip(&mut self) {
507        unsafe {
508            self.0.PopAxisAlignedClip();
509        }
510    }
511
512    pub(crate) fn clear(&mut self, color: D2D1_COLOR_F) {
513        unsafe {
514            self.0.Clear(&color);
515        }
516    }
517
518    pub(crate) fn set_transform(&mut self, transform: &D2D1_MATRIX_3X2_F) {
519        unsafe {
520            self.0.SetTransform(transform);
521        }
522    }
523
524    pub(crate) fn set_transform_identity(&mut self) {
525        unsafe {
526            self.0.SetTransform(&IDENTITY_MATRIX_3X2_F);
527        }
528    }
529
530    pub(crate) fn get_transform(&mut self) -> D2D1_MATRIX_3X2_F {
531        unsafe {
532            let mut empty = D2D1_MATRIX_3X2_F {
533                matrix: [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]],
534            };
535            self.0.GetTransform(&mut empty);
536            empty
537        }
538    }
539
540    pub(crate) fn fill_geometry(
541        &mut self,
542        geom: &Geometry,
543        brush: &Brush,
544        opacity_brush: Option<&Brush>,
545    ) {
546        unsafe {
547            self.0.FillGeometry(
548                geom.0.as_raw(),
549                brush.as_raw(),
550                opacity_brush.map(|b| b.as_raw()).unwrap_or(null_mut()),
551            );
552        }
553    }
554
555    pub(crate) fn draw_geometry(
556        &mut self,
557        geom: &Geometry,
558        brush: &Brush,
559        width: f32,
560        style: Option<&StrokeStyle>,
561    ) {
562        unsafe {
563            self.0.DrawGeometry(
564                geom.0.as_raw(),
565                brush.as_raw(),
566                width,
567                style.map(|ss| ss.0.as_raw()).unwrap_or(null_mut()),
568            );
569        }
570    }
571
572    pub(crate) fn draw_line(
573        &self,
574        line: Line,
575        brush: &Brush,
576        width: f32,
577        style: Option<&StrokeStyle>,
578    ) {
579        unsafe {
580            self.0.DrawLine(
581                to_point2f(line.p0),
582                to_point2f(line.p1),
583                brush.as_raw(),
584                width,
585                stroke_style_to_d2d(style),
586            );
587        }
588    }
589
590    pub(crate) fn draw_rect(
591        &self,
592        rect: Rect,
593        brush: &Brush,
594        width: f32,
595        style: Option<&StrokeStyle>,
596    ) {
597        unsafe {
598            self.0.DrawRectangle(
599                &rect_to_rectf(rect),
600                brush.as_raw(),
601                width,
602                stroke_style_to_d2d(style),
603            );
604        }
605    }
606
607    pub(crate) fn draw_rounded_rect(
608        &mut self,
609        rect: Rect,
610        radius: f64,
611        brush: &Brush,
612        width: f32,
613        style: Option<&StrokeStyle>,
614    ) {
615        let d2d_rounded_rect = rounded_rect_to_d2d(rect, radius);
616        unsafe {
617            self.0.DrawRoundedRectangle(
618                &d2d_rounded_rect,
619                brush.as_raw(),
620                width,
621                stroke_style_to_d2d(style),
622            );
623        }
624    }
625
626    pub(crate) fn draw_circle(
627        &self,
628        circle: Circle,
629        brush: &Brush,
630        width: f32,
631        style: Option<&StrokeStyle>,
632    ) {
633        unsafe {
634            self.0.DrawEllipse(
635                &circle_to_d2d(circle),
636                brush.as_raw(),
637                width,
638                stroke_style_to_d2d(style),
639            );
640        }
641    }
642
643    pub(crate) fn fill_rect(&self, rect: Rect, brush: &Brush) {
644        unsafe {
645            self.0.FillRectangle(&rect_to_rectf(rect), brush.as_raw());
646        }
647    }
648
649    pub(crate) fn fill_rounded_rect(&mut self, rect: Rect, radius: f64, brush: &Brush) {
650        let d2d_rounded_rect = rounded_rect_to_d2d(rect, radius);
651        unsafe {
652            self.0
653                .FillRoundedRectangle(&d2d_rounded_rect, brush.as_raw());
654        }
655    }
656
657    pub(crate) fn fill_circle(&self, circle: Circle, brush: &Brush) {
658        unsafe {
659            self.0.FillEllipse(&circle_to_d2d(circle), brush.as_raw());
660        }
661    }
662
663    pub(crate) fn create_layer(&mut self, size: Option<D2D1_SIZE_F>) -> Result<Layer, Error> {
664        unsafe {
665            let mut ptr = null_mut();
666            let hr = self.0.CreateLayer(optional(&size), &mut ptr);
667            wrap(hr, ptr, Layer)
668        }
669    }
670
671    // Should be &mut layer?
672    pub(crate) fn push_layer_mask(&mut self, mask: &Geometry, layer: &Layer) {
673        unsafe {
674            let params = D2D1_LAYER_PARAMETERS {
675                contentBounds: D2D1_RECT_F {
676                    left: f32::NEG_INFINITY,
677                    top: f32::NEG_INFINITY,
678                    right: f32::INFINITY,
679                    bottom: f32::INFINITY,
680                },
681                geometricMask: mask.0.as_raw(),
682                maskAntialiasMode: D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
683                maskTransform: IDENTITY_MATRIX_3X2_F,
684                opacity: 1.0,
685                opacityBrush: null_mut(),
686                layerOptions: D2D1_LAYER_OPTIONS_NONE,
687            };
688            self.0.deref().deref().PushLayer(&params, layer.0.as_raw());
689        }
690    }
691
692    pub(crate) fn pop_layer(&mut self) {
693        unsafe {
694            self.0.PopLayer();
695        }
696    }
697
698    /// This method should not be called directly. Callers should instead call
699    /// D2DRenderContext::solid_brush so values can be cached.
700    pub(crate) fn create_solid_color(&mut self, color: D2D1_COLOR_F) -> Result<Brush, Error> {
701        unsafe {
702            let mut ptr = null_mut();
703            let hr = self
704                .0
705                .CreateSolidColorBrush(&color, &DEFAULT_BRUSH_PROPERTIES, &mut ptr);
706            wrap(hr, ptr, |p| Brush(p.up()))
707        }
708    }
709
710    pub(crate) fn create_gradient_stops(
711        &mut self,
712        stops: &[D2D1_GRADIENT_STOP],
713    ) -> Result<GradientStopCollection, Error> {
714        unsafe {
715            // Should this assert or should we return an overflow error? Super
716            // unlikely in either case.
717            assert!(stops.len() <= 0xffff_ffff);
718            let mut ptr = null_mut();
719            // The `deref` is because there is a method of the same name in DeviceContext
720            // (with fancier color space controls). We'll take the vanilla one for now.
721            let hr = self.0.deref().deref().CreateGradientStopCollection(
722                stops.as_ptr(),
723                stops.len() as u32,
724                D2D1_GAMMA_2_2,
725                D2D1_EXTEND_MODE_CLAMP,
726                &mut ptr,
727            );
728            wrap(hr, ptr, GradientStopCollection)
729        }
730    }
731
732    #[allow(clippy::trivially_copy_pass_by_ref)]
733    pub(crate) fn create_linear_gradient(
734        &mut self,
735        props: &D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES,
736        stops: &GradientStopCollection,
737    ) -> Result<Brush, Error> {
738        unsafe {
739            let mut ptr = null_mut();
740            let hr = self.0.CreateLinearGradientBrush(
741                props,
742                &DEFAULT_BRUSH_PROPERTIES,
743                stops.0.as_raw(),
744                &mut ptr,
745            );
746            wrap(hr, ptr, |p| Brush(p.up()))
747        }
748    }
749
750    pub(crate) fn create_radial_gradient(
751        &mut self,
752        props: &D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES,
753        stops: &GradientStopCollection,
754    ) -> Result<Brush, Error> {
755        unsafe {
756            let mut ptr = null_mut();
757            let hr = self.0.CreateRadialGradientBrush(
758                props,
759                &DEFAULT_BRUSH_PROPERTIES,
760                stops.0.as_raw(),
761                &mut ptr,
762            );
763            wrap(hr, ptr, |p| Brush(p.up()))
764        }
765    }
766
767    // Buf is always interpreted as RGBA32 premultiplied.
768    pub(crate) fn create_bitmap(
769        &mut self,
770        width: usize,
771        height: usize,
772        buf: &[u8],
773        alpha_mode: D2D1_ALPHA_MODE,
774    ) -> Result<Bitmap, Error> {
775        // Maybe using TryInto would be more Rust-like.
776        // Note: value is set so that multiplying by 4 (for pitch) is valid.
777        assert!(width != 0 && width <= 0x3fff_ffff);
778        assert!(height != 0 && height <= 0xffff_ffff);
779        let size = D2D1_SIZE_U {
780            width: width as u32,
781            height: height as u32,
782        };
783        let format = D2D1_PIXEL_FORMAT {
784            format: DXGI_FORMAT_R8G8B8A8_UNORM,
785            alphaMode: alpha_mode,
786        };
787        let props = D2D1_BITMAP_PROPERTIES1 {
788            pixelFormat: format,
789            dpiX: 96.0,
790            dpiY: 96.0,
791            bitmapOptions: D2D1_BITMAP_OPTIONS_NONE,
792            colorContext: null_mut(),
793        };
794        let pitch = (width * 4) as u32;
795        unsafe {
796            let mut ptr = null_mut();
797            let hr = self.0.deref().CreateBitmap(
798                size,
799                buf.as_ptr() as *const c_void,
800                pitch,
801                &props,
802                &mut ptr,
803            );
804            wrap(hr, ptr, |ptr| Bitmap {
805                inner: ptr,
806                empty_image: false,
807            })
808        }
809    }
810
811    pub(crate) fn create_blank_bitmap(
812        &mut self,
813        width: usize,
814        height: usize,
815        dpi_scale: f32,
816    ) -> Result<Bitmap, Error> {
817        // Maybe using TryInto would be more Rust-like.
818        // Note: value is set so that multiplying by 4 (for pitch) is valid.
819        assert!(width != 0 && width <= 0x3fff_ffff);
820        assert!(height != 0 && height <= 0xffff_ffff);
821        let size = D2D1_SIZE_U {
822            width: width as u32,
823            height: height as u32,
824        };
825
826        unsafe {
827            let pixel_format = self.0.GetPixelFormat();
828            let props = D2D1_BITMAP_PROPERTIES1 {
829                pixelFormat: pixel_format,
830                dpiX: dpi_scale * 96.0,
831                dpiY: dpi_scale * 96.0,
832                bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET,
833                colorContext: null_mut(),
834            };
835
836            let mut ptr = null_mut();
837            let hr = self
838                .0
839                .deref()
840                .CreateBitmap(size, std::ptr::null(), 0, &props, &mut ptr);
841            wrap(hr, ptr, |ptr| Bitmap {
842                inner: ptr,
843                empty_image: false,
844            })
845        }
846    }
847
848    /// Create a valid empty image
849    ///
850    /// The image will actually be a 1x1 transparent pixel, but with the `empty_image` flag set so
851    /// rendering can be skipped.
852    pub(crate) fn create_empty_bitmap(&mut self) -> Result<Bitmap, Error> {
853        unsafe {
854            let mut ptr = null_mut();
855            let hr = self.0.deref().CreateBitmap(
856                D2D1_SIZE_U {
857                    width: 1,
858                    height: 1,
859                },
860                [0, 0, 0, 0].as_ptr() as *const c_void,
861                4,
862                &D2D1_BITMAP_PROPERTIES1 {
863                    pixelFormat: D2D1_PIXEL_FORMAT {
864                        format: DXGI_FORMAT_R8G8B8A8_UNORM,
865                        alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
866                    },
867                    dpiX: 96.0,
868                    dpiY: 96.0,
869                    bitmapOptions: D2D1_BITMAP_OPTIONS_NONE,
870                    colorContext: null_mut(),
871                },
872                &mut ptr,
873            );
874            wrap(hr, ptr, |ptr| Bitmap {
875                inner: ptr,
876                empty_image: true,
877            })
878        }
879    }
880
881    pub(crate) fn draw_text_layout(
882        &mut self,
883        origin: D2D1_POINT_2F,
884        layout: &TextLayout,
885        brush: &Brush,
886        options: D2D1_DRAW_TEXT_OPTIONS,
887    ) {
888        unsafe {
889            self.0
890                .DrawTextLayout(origin, layout.get_raw(), brush.as_raw(), options);
891        }
892    }
893
894    #[allow(clippy::trivially_copy_pass_by_ref)]
895    pub(crate) fn draw_bitmap(
896        &mut self,
897        bitmap: &Bitmap,
898        dst_rect: &D2D1_RECT_F,
899        opacity: f32,
900        interp_mode: D2D1_BITMAP_INTERPOLATION_MODE,
901        src_rect: Option<&D2D1_RECT_F>,
902    ) {
903        unsafe {
904            // derefs are so we get RenderTarget method rather than DeviceContext method.
905            // pointer casts are partly to undo that :)
906            self.0.deref().deref().DrawBitmap(
907                bitmap.inner.as_raw() as *mut ID2D1Bitmap,
908                dst_rect,
909                opacity,
910                interp_mode,
911                src_rect.map(|r| r as *const _).unwrap_or(null()),
912            );
913        }
914    }
915
916    // Discussion question: should we be using stddev instead of radius?
917    pub(crate) fn create_blur_effect(&mut self, radius: f64) -> Result<Effect, Error> {
918        unsafe {
919            let mut ptr = null_mut();
920            let hr = self
921                .0
922                .deref()
923                .CreateEffect(&CLSID_D2D1GaussianBlur, &mut ptr);
924            let effect = wrap(hr, ptr, Effect)?;
925            let val = radius as f32;
926            let hr = effect.0.SetValue(
927                D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION,
928                D2D1_PROPERTY_TYPE_FLOAT,
929                &val as *const _ as *const _,
930                std::mem::size_of_val(&val) as u32,
931            );
932            wrap_unit(hr)?;
933            Ok(effect)
934        }
935    }
936
937    // This is basically equivalent to an override of ID2D1DeviceContext::DrawImage method
938    // https://docs.microsoft.com/en-us/windows/win32/api/d2d1_1/nf-d2d1_1-id2d1devicecontext-drawimage(id2d1effect_constd2d1_point_2f_constd2d1_rect_f_d2d1_interpolation_mode_d2d1_composite_mode)
939    pub(crate) fn draw_image_effect(
940        &mut self,
941        effect: &Effect,
942        target_offset: Option<D2D1_POINT_2F>,
943        image_rect: Option<D2D1_RECT_F>,
944        interpolation_mode: D2D1_INTERPOLATION_MODE,
945        composite_mode: D2D1_COMPOSITE_MODE,
946    ) {
947        unsafe {
948            let mut ptr = null_mut();
949            effect.0.GetOutput(&mut ptr);
950            let output = ComPtr::from_raw(ptr);
951            self.0.DrawImage(
952                output.as_raw(),
953                optional(&target_offset),
954                optional(&image_rect),
955                interpolation_mode,
956                composite_mode,
957            );
958        }
959    }
960
961    // Note: the pixel size is not specified. As a potential future optimization,
962    // we can be more sophisticated in choosing a pixel size.
963    pub(crate) fn create_compatible_render_target(
964        &mut self,
965        width_f: f32,
966        height_f: f32,
967    ) -> Result<BitmapRenderTarget, Error> {
968        unsafe {
969            let mut ptr = null_mut();
970            let size_f = D2D1_SIZE_F {
971                width: width_f,
972                height: height_f,
973            };
974            // It might be slightly cleaner to not specify the format, but we want
975            // premultiplied alpha even if for whatever reason the parent render target
976            // doesn't have that.
977            let format = D2D1_PIXEL_FORMAT {
978                format: DXGI_FORMAT_R8G8B8A8_UNORM,
979                alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
980            };
981            let options = D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE;
982            let hr =
983                self.0
984                    .CreateCompatibleRenderTarget(&size_f, null(), &format, options, &mut ptr);
985            wrap(hr, ptr, BitmapRenderTarget)
986        }
987    }
988}
989
990impl PathGeometry {
991    pub fn open(&mut self) -> Result<GeometrySink<'_>, Error> {
992        unsafe {
993            let mut ptr = null_mut();
994            let hr = (self.0).Open(&mut ptr);
995            wrap(hr, ptr, |ptr| GeometrySink {
996                ptr,
997                marker: Default::default(),
998            })
999        }
1000    }
1001}
1002
1003// Note: this impl has not been audited for safety. It might be possible
1004// to provoke a crash by doing things in the wrong order.
1005impl<'a> GeometrySink<'a> {
1006    pub fn set_fill_mode(&mut self, fill_rule: FillRule) {
1007        let fill_mode = match fill_rule {
1008            FillRule::EvenOdd => D2D1_FILL_MODE_ALTERNATE,
1009            FillRule::NonZero => D2D1_FILL_MODE_WINDING,
1010        };
1011        unsafe {
1012            self.ptr.SetFillMode(fill_mode);
1013        }
1014    }
1015
1016    pub fn add_bezier(
1017        &mut self,
1018        point1: D2D1_POINT_2F,
1019        point2: D2D1_POINT_2F,
1020        point3: D2D1_POINT_2F,
1021    ) {
1022        let seg = D2D1_BEZIER_SEGMENT {
1023            point1,
1024            point2,
1025            point3,
1026        };
1027        unsafe {
1028            self.ptr.AddBezier(&seg);
1029        }
1030    }
1031
1032    pub fn add_quadratic_bezier(&mut self, point1: D2D1_POINT_2F, point2: D2D1_POINT_2F) {
1033        let seg = D2D1_QUADRATIC_BEZIER_SEGMENT { point1, point2 };
1034        unsafe {
1035            self.ptr.AddQuadraticBezier(&seg);
1036        }
1037    }
1038
1039    pub fn add_line(&mut self, point: D2D1_POINT_2F) {
1040        unsafe {
1041            self.ptr.AddLine(point);
1042        }
1043    }
1044
1045    pub fn begin_figure(&mut self, start: D2D1_POINT_2F, is_filled: bool) {
1046        unsafe {
1047            let figure_end = if is_filled {
1048                D2D1_FIGURE_BEGIN_FILLED
1049            } else {
1050                D2D1_FIGURE_BEGIN_HOLLOW
1051            };
1052            self.ptr.BeginFigure(start, figure_end);
1053        }
1054    }
1055
1056    pub fn end_figure(&mut self, is_closed: bool) {
1057        unsafe {
1058            let figure_end = if is_closed {
1059                D2D1_FIGURE_END_CLOSED
1060            } else {
1061                D2D1_FIGURE_END_OPEN
1062            };
1063            self.ptr.EndFigure(figure_end);
1064        }
1065    }
1066
1067    // A case can be made for doing this in the drop instead.
1068    pub fn close(self) -> Result<(), Error> {
1069        unsafe { wrap_unit(self.ptr.Close()) }
1070    }
1071}
1072
1073/// This might not be needed.
1074impl Bitmap {
1075    pub fn get_size(&self) -> D2D1_SIZE_F {
1076        unsafe { self.inner.GetSize() }
1077    }
1078
1079    pub(crate) fn copy_from_render_target(
1080        &mut self,
1081        dest_point: D2D1_POINT_2U,
1082        rt: &mut DeviceContext,
1083        src_rect: D2D1_RECT_U,
1084    ) {
1085        unsafe {
1086            let rt = rt.get_raw() as *mut _;
1087            self.inner.CopyFromRenderTarget(&dest_point, rt, &src_rect);
1088        }
1089    }
1090}
1091
1092impl Effect {
1093    /// Set the effect's input.
1094    ///
1095    /// Safety concern: is this capturing the lifetime of the input? What happens
1096    /// if the input is deallocated before the effect is actually run? This is not
1097    /// adequately documented, but we will be conservative in actual usage.
1098    pub(crate) fn set_input(&self, index: u32, input: &ID2D1Image) {
1099        unsafe {
1100            self.0.SetInput(index, input, TRUE);
1101        }
1102    }
1103}
1104
1105impl BitmapRenderTarget {
1106    // Get the bitmap.
1107    //
1108    // We could return a wrapped object, but it doesn't seem worth it.
1109    pub(crate) fn get_bitmap(&self) -> Result<ComPtr<ID2D1Bitmap>, Error> {
1110        unsafe {
1111            let mut ptr = null_mut();
1112            let hr = self.0.GetBitmap(&mut ptr);
1113            wrap(hr, ptr, |com_ptr| com_ptr)
1114        }
1115    }
1116}
1117
1118// Note: this approach is a bit different than other wrapped types; it's basically
1119// optimized for usage in unsafe code.
1120impl Deref for BitmapRenderTarget {
1121    type Target = ID2D1BitmapRenderTarget;
1122
1123    fn deref(&self) -> &Self::Target {
1124        &self.0
1125    }
1126}
1127
1128impl Brush {
1129    // This impl is provided for blurred rectangle drawing. There are other ways
1130    // to factor this (for example, by making methods available on the bitmap
1131    // render target).
1132    pub(crate) fn as_raw(&self) -> *mut ID2D1Brush {
1133        self.0.as_raw()
1134    }
1135}
1136
1137mod tests {
1138    use super::*;
1139
1140    #[test]
1141    fn geom_builder() {
1142        let mut factory = D2DFactory::new().unwrap();
1143        let mut p = factory.create_path_geometry().unwrap();
1144        let mut s1 = p.open().unwrap();
1145        // Note: if the next two lines are swapped, it's a compile
1146        // error.
1147        s1.close();
1148        if let Ok(mut s2) = p.open() {
1149            s2.close();
1150        }
1151    }
1152}