piet_direct2d/
lib.rs

1// Copyright 2019 the Piet Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4#![cfg(windows)]
5// allows for nice formatting for e.g. new_buf[i * 4 + 0] = premul(buf[i * 4 + 0, a)
6#![allow(clippy::identity_op)]
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8#![deny(clippy::trivially_copy_pass_by_ref)]
9
10//! The Direct2D backend for the Piet 2D graphics abstraction.
11
12mod 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    /// The context state stack. There is always at least one, until finishing.
54    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    // Note: when we start pushing both layers and axis aligned clips, this will
68    // need to keep track of which is which. But for now, keep it simple.
69    n_layers_pop: usize,
70}
71
72impl<'b, 'a: 'b> D2DRenderContext<'a> {
73    /// Create a new Piet RenderContext for the Direct2D DeviceContext.
74    ///
75    /// TODO: check signature.
76    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        // This is an unwrap because we protect the invariant.
94        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    /// Check whether drawing operations have finished.
102    ///
103    /// Clients should call this before extracting or presenting the contents of
104    /// the drawing surface.
105    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
113// The setting of 1e-3 is extremely conservative (absolutely no
114// differences should be visible) but setting a looser tolerance is
115// likely a tiny performance improvement. We could fine-tune based on
116// empirical study of both quality and performance.
117const 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    // TODO: Do something special for line?
126    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        // Remove clippings
201        for _ in 0..self.layers.len() {
202            self.rt.pop_layer();
203        }
204
205        // Reset transform to identity
206        let old_transform = self.rt.get_transform();
207        self.rt.set_transform_identity();
208
209        if let Some(rect) = region.into() {
210            // Clear only the given rect
211            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            // Clear whole canvas
216            self.rt.clear(color_to_colorf(color));
217        }
218
219        // Restore transform
220        self.rt.set_transform(&old_transform);
221
222        // Restore clippings
223        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        // TODO: set size based on bbox of shape.
297        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        // TODO: bounding box for text
322        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        // Move this code into impl to avoid duplication with transform?
340        self.rt
341            .set_transform(&affine_to_matrix3x2f(self.current_transform()));
342
343        Ok(())
344    }
345
346    // Discussion question: should this subsume EndDraw, with BeginDraw on
347    // D2DRenderContext creation? I'm thinking not, as the shell might want
348    // to do other stuff, possibly related to incremental paint.
349    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        // This is an unwrap because we protect the invariant.
365        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        // CreateBitmap will fail if we try to make an empty image. To solve this, we change an
377        // empty image into 1x1 transparent image. Not ideal, but prevents a crash. TODO find a
378        // better solution.
379        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        // TODO: this method _really_ needs error checking, so much can go wrong...
394        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                // TODO (performance): this would be soooo much faster with SIMD
416                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                // it seems like there's no good way to create a 1-channel bitmap
444                // here? I am not alone:
445                // https://stackoverflow.com/questions/44270215/direct2d-fails-when-drawing-a-single-channel-bitmap
446                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            // This should be unreachable, we caught it above.
459            _ => 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        // TODO: This transformation is untested with the current test pictures
506        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        // TODO: This transformation is untested with the current test pictures
514        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        // Clear layers from the render target
528        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        // Restore cleared layers
535        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        // TODO: do something special (or nothing at all) for line?
555        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    // This is split out to unify error reporting, as there are lots of opportunities for
614    // errors in resource creation.
615    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        // Note: we're being fairly dumb about choosing the bitmap size, not taking
625        // dpi scaling into account.
626        let brt = self.rt.create_compatible_render_target(widthf, heightf)?;
627        // Is it necessary to clear, or can we count on it being in a cleared
628        // state when it's created?
629        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        // It might be slightly cleaner to create the effect on `brt`, but it should
646        // be fine, as it's "compatible".
647        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        // source or destination are empty
680        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}