From d955b8cd308b8f5b442d7f25673352bfe070687e Mon Sep 17 00:00:00 2001 From: longmathemagician Date: Mon, 25 Jul 2022 18:39:58 -0700 Subject: [PATCH 1/4] Reimplement blur effect with cleaner D2D calls --- piet-direct2d/src/d2d.rs | 17 +++-- piet-direct2d/src/lib.rs | 139 ++++++++++++++++++++++++++++++++++++- piet/src/null_renderer.rs | 8 +++ piet/src/render_context.rs | 4 ++ 4 files changed, 159 insertions(+), 9 deletions(-) diff --git a/piet-direct2d/src/d2d.rs b/piet-direct2d/src/d2d.rs index 185b1205..44e09a9a 100644 --- a/piet-direct2d/src/d2d.rs +++ b/piet-direct2d/src/d2d.rs @@ -26,11 +26,12 @@ use winapi::um::d2d1::{ ID2D1PathGeometry, ID2D1RectangleGeometry, ID2D1RenderTarget, ID2D1RoundedRectangleGeometry, ID2D1SolidColorBrush, ID2D1StrokeStyle, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1_BEZIER_SEGMENT, D2D1_BITMAP_INTERPOLATION_MODE, D2D1_BRUSH_PROPERTIES, D2D1_COLOR_F, - D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, D2D1_DEBUG_LEVEL_NONE, D2D1_DEBUG_LEVEL_WARNING, - D2D1_DRAW_TEXT_OPTIONS, D2D1_EXTEND_MODE_CLAMP, D2D1_FACTORY_OPTIONS, - D2D1_FACTORY_TYPE_MULTI_THREADED, D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_BEGIN_HOLLOW, - D2D1_FIGURE_END_CLOSED, D2D1_FIGURE_END_OPEN, D2D1_FILL_MODE_ALTERNATE, D2D1_FILL_MODE_WINDING, - D2D1_GAMMA_2_2, D2D1_GRADIENT_STOP, D2D1_LAYER_OPTIONS_NONE, D2D1_LAYER_PARAMETERS, + D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, D2D1_DEBUG_LEVEL_INFORMATION, + D2D1_DEBUG_LEVEL_NONE, D2D1_DEBUG_LEVEL_WARNING, D2D1_DRAW_TEXT_OPTIONS, + D2D1_EXTEND_MODE_CLAMP, D2D1_FACTORY_OPTIONS, D2D1_FACTORY_TYPE_MULTI_THREADED, + D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_BEGIN_HOLLOW, D2D1_FIGURE_END_CLOSED, + D2D1_FIGURE_END_OPEN, D2D1_FILL_MODE_ALTERNATE, D2D1_FILL_MODE_WINDING, D2D1_GAMMA_2_2, + D2D1_GRADIENT_STOP, D2D1_LAYER_OPTIONS_NONE, D2D1_LAYER_PARAMETERS, D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES, D2D1_MATRIX_3X2_F, D2D1_POINT_2F, D2D1_POINT_2U, D2D1_QUADRATIC_BEZIER_SEGMENT, D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES, D2D1_RECT_F, D2D1_RECT_U, D2D1_SIZE_F, D2D1_SIZE_U, D2D1_STROKE_STYLE_PROPERTIES, @@ -232,7 +233,7 @@ impl D2DFactory { // The debug layer should never be active in the release version of an application. // https://docs.microsoft.com/en-us/windows/win32/Direct2D/direct2ddebuglayer-overview debugLevel: match cfg!(debug_assertions) { - true => D2D1_DEBUG_LEVEL_WARNING, + true => D2D1_DEBUG_LEVEL_INFORMATION, false => D2D1_DEBUG_LEVEL_NONE, }, }, @@ -1049,6 +1050,10 @@ impl Bitmap { unsafe { self.inner.GetSize() } } + pub(crate) unsafe fn get_comptr(&self) -> &ComPtr { + &self.inner + } + pub(crate) fn copy_from_render_target( &mut self, dest_point: D2D1_POINT_2U, diff --git a/piet-direct2d/src/lib.rs b/piet-direct2d/src/lib.rs index 39ae845c..e26398df 100644 --- a/piet-direct2d/src/lib.rs +++ b/piet-direct2d/src/lib.rs @@ -13,14 +13,25 @@ mod text; use std::borrow::Cow; use std::ops::Deref; +use std::ptr::null_mut; use associative_cache::{AssociativeCache, Capacity1024, HashFourWay, RoundRobinReplacement}; use winapi::um::d2d1::{ - D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, - D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES, D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES, + ID2D1Image, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES, + D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES, +}; +use winapi::um::d2d1_1::{ + D2D1_COMPOSITE_MODE_SOURCE_COPY, D2D1_COMPOSITE_MODE_SOURCE_OVER, + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, D2D1_INTERPOLATION_MODE_LINEAR, D2D1_PROPERTY_TYPE_FLOAT, + D2D1_PROPERTY_TYPE_UNKNOWN, +}; +use winapi::um::d2d1effects::{ + CLSID_D2D1GaussianBlur, D2D1_BORDER_MODE_HARD, D2D1_DIRECTIONALBLUR_OPTIMIZATION_SPEED, + D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION, + D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, }; -use winapi::um::d2d1_1::{D2D1_COMPOSITE_MODE_SOURCE_OVER, D2D1_INTERPOLATION_MODE_LINEAR}; use winapi::um::dcommon::{D2D1_ALPHA_MODE_IGNORE, D2D1_ALPHA_MODE_PREMULTIPLIED}; use piet::kurbo::{Affine, PathEl, Point, Rect, Shape, Size}; @@ -29,6 +40,7 @@ use piet::{ Color, Error, FixedGradient, Image, ImageFormat, InterpolationMode, IntoBrush, RenderContext, StrokeStyle, }; +use wio::com::ComPtr; use crate::d2d::{wrap_unit, Layer}; pub use crate::d2d::{D2DDevice, D2DFactory, DeviceContext as D2DDeviceContext}; @@ -499,6 +511,127 @@ impl<'a> RenderContext for D2DRenderContext<'a> { eprintln!("error in drawing blurred rect: {:?}", e); } } + + fn blur_image( + &mut self, + _image: &Self::Image, + _blur_radius: f64, + ) -> Result { + // Create image size objects + let image_size_raw = _image.get_size(); + let (dpi_scale, _) = self.rt.get_dpi_scale(); + let image_size = Rect::new( + 0.0, + 0.0, + (image_size_raw.width * dpi_scale) as f64, + (image_size_raw.height * dpi_scale) as f64, + ); + + // Create a blank target bitmap to receive the blurred image + let bitmap_target = self.rt.create_blank_bitmap( + image_size.width() as usize, + image_size.height() as usize, + dpi_scale, + )?; + + unsafe { + { + // Get reference to ID2D1Device + let mut device_raw = null_mut(); + self.rt.get_comptr().GetDevice(&mut device_raw); + let device_comptr = ComPtr::from_raw(device_raw); + + // Create a new ID2D1DeviceContext + let mut device_context_raw = null_mut(); + let _hr = device_comptr + .CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &mut device_context_raw); + if _hr != 0x0 { + return Err(Error::NotSupported); + } + let device_context_comptr = ComPtr::from_raw(device_context_raw); + + // Set the device context target to the source + device_context_comptr + .SetTarget(bitmap_target.get_comptr().as_raw() as *const ID2D1Image); + + // Begin drawing operations + device_context_comptr.BeginDraw(); + + // Create ID2D1Effect + let mut blur_effect_raw = null_mut(); + let _hr = device_context_comptr + .CreateEffect(&CLSID_D2D1GaussianBlur, &mut blur_effect_raw); + if _hr != 0x0 { + return Err(Error::NotSupported); + } + let blur_effect_comptr = ComPtr::from_raw(blur_effect_raw); + + // Set blur radius + let blur_radius_f32 = _blur_radius as f32; + let _hr = blur_effect_comptr.SetValue( + D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, + D2D1_PROPERTY_TYPE_FLOAT, + &blur_radius_f32 as *const _ as *const _, + std::mem::size_of_val(&blur_radius_f32) as u32, + ); + if _hr != 0x0 { + return Err(Error::NotSupported); + } + + // Set blur optimization level to high + let blur_optimization_level = D2D1_DIRECTIONALBLUR_OPTIMIZATION_SPEED; + let _hr = blur_effect_comptr.SetValue( + D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION, + D2D1_PROPERTY_TYPE_UNKNOWN, + &blur_optimization_level as *const _ as *const _, + std::mem::size_of_val(&blur_optimization_level) as u32, + ); + if _hr != 0x0 { + return Err(Error::NotSupported); + } + + // Set blur border mode to wrapped + let blur_border_mode = D2D1_BORDER_MODE_HARD; + let _hr = blur_effect_comptr.SetValue( + D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, + D2D1_PROPERTY_TYPE_UNKNOWN, + &blur_border_mode as *const _ as *const _, + std::mem::size_of_val(&blur_border_mode) as u32, + ); + if _hr != 0x0 { + return Err(Error::NotSupported); + } + + // Set blur effect input to source image + blur_effect_comptr.SetInput( + 0, + _image.get_comptr().as_raw() as *const ID2D1Image, + winapi::shared::minwindef::TRUE, + ); + + // Request an ID2D1Image from the effect + let mut blur_effect_image_raw = null_mut(); + blur_effect_comptr.GetOutput(&mut blur_effect_image_raw); + let blur_effect_image_comptr = ComPtr::from_raw(blur_effect_image_raw); + + // Draw the now blurred image to the target bitmap + device_context_comptr.DrawImage( + blur_effect_image_comptr.as_raw(), + &to_point2f((0.0f32, 0.0f32)), + &rect_to_rectf(image_size), + D2D1_INTERPOLATION_MODE_LINEAR, + D2D1_COMPOSITE_MODE_SOURCE_COPY, + ); + + // End drawing operations + let mut tag1 = 0; + let mut tag2 = 0; + device_context_comptr.EndDraw(&mut tag1, &mut tag2); + } + } + + Ok(bitmap_target) + } } impl<'a> D2DRenderContext<'a> { diff --git a/piet/src/null_renderer.rs b/piet/src/null_renderer.rs index ae7ab4f0..fa5677f3 100644 --- a/piet/src/null_renderer.rs +++ b/piet/src/null_renderer.rs @@ -132,6 +132,14 @@ impl RenderContext for NullRenderContext { fn current_transform(&self) -> Affine { Affine::default() } + + fn blur_image( + &mut self, + _image: &Self::Image, + _blur_radius: f64, + ) -> Result { + Ok(NullImage) + } } impl Text for NullText { diff --git a/piet/src/render_context.rs b/piet/src/render_context.rs index d2994cff..f19ca973 100644 --- a/piet/src/render_context.rs +++ b/piet/src/render_context.rs @@ -267,6 +267,10 @@ where /// Returns the transformations currently applied to the context. fn current_transform(&self) -> Affine; + + /// Returns a blurred copy of the provided image. + fn blur_image(&mut self, _image: &Self::Image, _blur_radius: f64) + -> Result; } /// A trait for various types that can be used as brushes. From b2154e54285df4c1cec7c6b669ed660b4ffcb777 Mon Sep 17 00:00:00 2001 From: Stephen Gibbel Date: Mon, 25 Jul 2022 19:47:45 -0700 Subject: [PATCH 2/4] Add method stubs for other backend platforms --- piet-cairo/src/lib.rs | 8 ++++++++ piet-coregraphics/src/lib.rs | 8 ++++++++ piet-svg/src/lib.rs | 8 ++++++++ piet-web/src/lib.rs | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/piet-cairo/src/lib.rs b/piet-cairo/src/lib.rs index 0e0498c5..246768ef 100644 --- a/piet-cairo/src/lib.rs +++ b/piet-cairo/src/lib.rs @@ -384,6 +384,14 @@ impl<'a> RenderContext for CairoRenderContext<'a> { Err(err) => self.error = Err(err), } } + + fn blur_image( + &mut self, + _image: &Self::Image, + _blur_radius: f64, + ) -> Result { + Err(Error::Unimplemented) + } } impl<'a> IntoBrush> for Brush { diff --git a/piet-coregraphics/src/lib.rs b/piet-coregraphics/src/lib.rs index 07eaa789..d5880e71 100644 --- a/piet-coregraphics/src/lib.rs +++ b/piet-coregraphics/src/lib.rs @@ -439,6 +439,14 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { fn status(&mut self) -> Result<(), Error> { Ok(()) } + + fn blur_image( + &mut self, + _image: &Self::Image, + _blur_radius: f64, + ) -> Result { + Err(Error::Unimplemented) + } } impl<'a> IntoBrush> for Brush { diff --git a/piet-svg/src/lib.rs b/piet-svg/src/lib.rs index a16add47..77922315 100644 --- a/piet-svg/src/lib.rs +++ b/piet-svg/src/lib.rs @@ -450,6 +450,14 @@ impl piet::RenderContext for RenderContext { // TODO blur (perhaps using SVG filters) self.fill(rect, brush) } + + fn blur_image( + &mut self, + _image: &Self::Image, + _blur_radius: f64, + ) -> Result { + Err(Error::Unimplemented) + } } fn draw_image( diff --git a/piet-web/src/lib.rs b/piet-web/src/lib.rs index 128fd680..e024999f 100644 --- a/piet-web/src/lib.rs +++ b/piet-web/src/lib.rs @@ -378,6 +378,14 @@ impl RenderContext for WebRenderContext<'_> { .fill_rect(rect.x0, rect.y0, rect.width(), rect.height()); self.ctx.set_shadow_color("none"); } + + fn blur_image( + &mut self, + _image: &Self::Image, + _blur_radius: f64, + ) -> Result { + Err(Error::Unimplemented) + } } fn draw_image( From 422ea8119c7e2def7b084a324ad302a25feceac6 Mon Sep 17 00:00:00 2001 From: Stephen Gibbel Date: Mon, 25 Jul 2022 21:17:58 -0700 Subject: [PATCH 3/4] Reimplement Cairo blur method, slightly slower but safer than the previous implementation. --- piet-cairo/src/lib.rs | 110 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/piet-cairo/src/lib.rs b/piet-cairo/src/lib.rs index 246768ef..e14117cf 100644 --- a/piet-cairo/src/lib.rs +++ b/piet-cairo/src/lib.rs @@ -390,7 +390,115 @@ impl<'a> RenderContext for CairoRenderContext<'a> { _image: &Self::Image, _blur_radius: f64, ) -> Result { - Err(Error::Unimplemented) + // Set image parameters + let size = _image.size(); + let width = size.width as usize; + let height = size.height as usize; + let image_channel_count: usize = 4; + + // Calcuale blur parameters + let blur_radius_min: usize = 2; + let blur_increment_size: usize = 3; + let blur_iterations: usize = + (_blur_radius as usize - blur_radius_min) / blur_increment_size; + + // Create a new image surface to hold the blurred image + let mut target_surface = ImageSurface::create(Format::ARgb32, width as i32, height as i32) + .map_err(convert_error)?; + + // Encapsulate drawing context so that the pointer reference to the + // image surface is dropped before we request the data + { + let target_ctx = Context::new(&target_surface).map_err(convert_error)?; + target_ctx + .set_source_surface(&_image.0, 0.0, 0.0) + .map_err(convert_error)?; + target_ctx.rectangle(0.0, 0.0, size.width, size.height); + target_ctx.fill().map_err(convert_error)?; + } + + // Encapsulate blur so that borrows are dropped before returning the image + { + let mut blurred_surface_data = target_surface + .data() + .expect("Failed to address surface data"); + let mut inner_blur_loop = |current_pass_radius: usize| { + let original_surface_data_copy: Vec = blurred_surface_data.to_vec(); + + // precompute loop constants + let edge_offset = current_pass_radius * image_channel_count; + let width_offset_adjusted = + width * image_channel_count - edge_offset - image_channel_count; + let height_offset_adjusted = + height * image_channel_count - edge_offset - image_channel_count; + let current_pass_radius_offset = current_pass_radius * image_channel_count; + + //parse colum elements (down) + for j in (0..(height * image_channel_count)).step_by(image_channel_count) { + //parse row elements (to the right) + for i in (0..(width * image_channel_count)).step_by(image_channel_count) { + for k in 0..(image_channel_count - 1) { + let x = if edge_offset > i { + if width_offset_adjusted > edge_offset { + edge_offset + } else { + width_offset_adjusted + } + } else { + if width_offset_adjusted > i { + i + } else { + width_offset_adjusted + } + }; + // let x = (width_offset_adjusted).min(edge_offset.max(i)); + let y = (height_offset_adjusted).min((edge_offset).max(j)); + + let mut sum: f32 = 0.; // channel accumulator + + // add the channel value from the current pixel + sum += (original_surface_data_copy[k + y * width + x] as f32) * 0.25; + + // add the channel values from the surrounding four corner pixels at radius distance + sum += original_surface_data_copy[k + + (y - current_pass_radius_offset) * width + + x + - current_pass_radius_offset] + as f32 + * 0.1875; // top left pixel + sum += original_surface_data_copy[k + + (y - current_pass_radius_offset) * width + + x + + current_pass_radius_offset] + as f32 + * 0.1875; // top right pixel + sum += original_surface_data_copy[k + + (y + current_pass_radius_offset) * width + + x + - current_pass_radius_offset] + as f32 + * 0.1875; // bottom left pixel + sum += original_surface_data_copy[k + + (y + current_pass_radius_offset) * width + + x + + current_pass_radius_offset] + as f32 + * 0.1875; // bottom right pixel + + blurred_surface_data[k + j * width + i] = sum as u8; + } + } + } + }; + + for i in (blur_radius_min..(blur_increment_size * blur_iterations)) + .step_by(blur_increment_size) + { + inner_blur_loop(i); + } + } + + Ok(CairoImage(target_surface)) } } From dab38b513bdeba2790914c88a173502238c2cd3a Mon Sep 17 00:00:00 2001 From: longmathemagician Date: Wed, 17 Aug 2022 15:59:51 -0700 Subject: [PATCH 4/4] Software blur for coregraphics --- piet-coregraphics/src/lib.rs | 94 +++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/piet-coregraphics/src/lib.rs b/piet-coregraphics/src/lib.rs index d5880e71..e7288386 100644 --- a/piet-coregraphics/src/lib.rs +++ b/piet-coregraphics/src/lib.rs @@ -445,7 +445,99 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { _image: &Self::Image, _blur_radius: f64, ) -> Result { - Err(Error::Unimplemented) + // Set image parameters + let size = _image.size(); + let width = size.width as usize; + let height = size.height as usize; + let image_channel_count: usize = 4; + + // Calcuale blur parameters + let blur_radius_min: usize = 2; + let blur_increment_size: usize = 3; + let blur_iterations: usize = + (_blur_radius as usize - blur_radius_min) / blur_increment_size; + + let image = _image.as_ref().ok_or(Error::InvalidInput)?; + let data = image.data(); + let source_data = data.bytes(); + + let mut blurred_surface_data = source_data.to_vec(); + let mut inner_blur_loop = |current_pass_radius: usize| { + let original_surface_data_copy: Vec = blurred_surface_data.to_vec(); + + // precompute loop constants + let edge_offset = current_pass_radius * image_channel_count; + let width_offset_adjusted = + width * image_channel_count - edge_offset - image_channel_count; + let height_offset_adjusted = + height * image_channel_count - edge_offset - image_channel_count; + let current_pass_radius_offset = current_pass_radius * image_channel_count; + + //parse colum elements (down) + for j in (0..(height * image_channel_count)).step_by(image_channel_count) { + //parse row elements (to the right) + for i in (0..(width * image_channel_count)).step_by(image_channel_count) { + for k in 0..(image_channel_count - 1) { + let x = if edge_offset > i { + if width_offset_adjusted > edge_offset { + edge_offset + } else { + width_offset_adjusted + } + } else { + if width_offset_adjusted > i { + i + } else { + width_offset_adjusted + } + }; + // let x = (width_offset_adjusted).min(edge_offset.max(i)); + let y = (height_offset_adjusted).min((edge_offset).max(j)); + + let mut sum: f32 = 0.; // channel accumulator + + // add the channel value from the current pixel + sum += (original_surface_data_copy[k + y * width + x] as f32) * 0.25; + + // add the channel values from the surrounding four corner pixels at radius distance + sum += original_surface_data_copy[k + + (y - current_pass_radius_offset) * width + + x + - current_pass_radius_offset] + as f32 + * 0.1875; // top left pixel + sum += original_surface_data_copy[k + + (y - current_pass_radius_offset) * width + + x + + current_pass_radius_offset] + as f32 + * 0.1875; // top right pixel + sum += original_surface_data_copy[k + + (y + current_pass_radius_offset) * width + + x + - current_pass_radius_offset] + as f32 + * 0.1875; // bottom left pixel + sum += original_surface_data_copy[k + + (y + current_pass_radius_offset) * width + + x + + current_pass_radius_offset] + as f32 + * 0.1875; // bottom right pixel + + blurred_surface_data[k + j * width + i] = sum as u8; + } + } + } + }; + + for i in (blur_radius_min..(blur_increment_size * blur_iterations)) + .step_by(blur_increment_size) + { + inner_blur_loop(i); + } + + self.make_image(width, height, &blurred_surface_data, ImageFormat::RgbaPremul) } }