Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blur methods for PietImages #527

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions piet-cairo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,122 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
Err(err) => self.error = Err(err),
}
}

fn blur_image(
&mut self,
_image: &Self::Image,
_blur_radius: f64,
) -> Result<Self::Image, Error> {
// 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<u8> = 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))
}
}

impl<'a> IntoBrush<CairoRenderContext<'a>> for Brush {
Expand Down
100 changes: 100 additions & 0 deletions piet-coregraphics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,106 @@ 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<Self::Image, Error> {
// 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<u8> = 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)
}
}

impl<'a> IntoBrush<CoreGraphicsContext<'a>> for Brush {
Expand Down
17 changes: 11 additions & 6 deletions piet-direct2d/src/d2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
},
},
Expand Down Expand Up @@ -1049,6 +1050,10 @@ impl Bitmap {
unsafe { self.inner.GetSize() }
}

pub(crate) unsafe fn get_comptr(&self) -> &ComPtr<ID2D1Bitmap1> {
&self.inner
}

pub(crate) fn copy_from_render_target(
&mut self,
dest_point: D2D1_POINT_2U,
Expand Down
Loading